原理篇
spring与springboot区别
spring是承载容器
springboot做的主要工作:
①简化配置(省去了spring中配置xml,引入application.yml文件)
②为我们提供了 spring-boot-starter-web 依赖,这个依赖包含了Tomcat和springmvc等一系列的web依赖
③自动配置(基于自动配置包
spring-boot-starter-autoconfig
)
1.自动配置的工作流程
1.1 bean的加载方式
方式一:配置文件+<bean/>
标签
- 缺点:配置bean太繁琐
方式二:配置文件扫描+注解定义bean⭐️
@configuration注解和@component注解功能相似,但是@configuration注解实现的是单例配置bean
-
获取bean方式
-
①通过配置文件,扫描指定包,加载bean
②通过注解声明bean,本地的加上@component注解;第三方声明一个配置类,提供一个返回第三方对象的方法(方法名为bean的id名),然后配置为bean即可
- 缺点:只需要一个配置文件,可否干掉配置文件?
方式三:注解方式声明配置类
-
声明一个总配置类,然后加上@componentScan注解,扫描要加载bean的包即可
1.使用FactroyBean接口⭐️
介绍:
@Bean注解所得到的bean对象不一定是该类本身,如果一个类实现了FactoryBean接口,那么配置到@bean中,返回的对象是其实现FactoryBean接口中的getObject方法返回的对象
工厂模式特点:
- 方法返回时工厂bean对象,但是交给Spring容器管理的是bean对象的getObject()方法后的对象
- 工厂接口交给Spring容器管理的bean可设置单例或者多例
- 只有在自己调用工厂方法时,获取的是工厂对象
-
实现FactoryBean接口好处:
可以在给spring配置一个bean的时候,**检查bean对象是否满足一些条件,**或者是给bean初始化一些属性,然后再交给bean管理
-
使用FactoryBean方法:
①实现接口,要注意加上泛型
②实现三个方法(分别为真正配置的bean,bean的class类型,交给spring管理的模式)
③配置bean注解,交给spring管理
交给spring一般是单例模式的类
-
使用FactoryBean举例:
getBean(“book”)得到的是book对象
但是如果通过这个配置类调book方法,返回的是BookFactoryBean对象
也就是说BookFactoryBean会造自己的对象,但是不会送到Spring容器中,只有当外部方法调用的时候会返回BookFactoryBean对象(方法特性)
2.注解格式导入XML格式配置的bean
@ImportResource注解导入xml注解配置的bean,兼容老项目
3.proxyBeanMethods属性(探讨@configuration注解)⭐️
推荐看这篇文章
Spring中Bean的单例和多例简单总结
Configuration
总结:一句话概括就是@Configuration
中所有带@Bean
注解的方法都会被动态代理,因此调用该方法返回的都是同一个实例。@component注解和@configuration注解相同和区别:✏️
相同:@component注解默认交给Spring容器管理的bean和@configuration注解一样,都是单例模式的
不同:
①@configuration交给IOC容器管理的最外层bean(比如Animal)是一个代理类对象,不是Animal类对象本身,通过代理类对象调用方法获取的是IOC容器中的对象
②@component交给IOC管理的就是本身对象(Animal和Dog),但是获取Animal后调用dog获取的是一个新对象,这里没有做到单例
Spring中单例和多例的理解:
✏️重在向Spring容器中请求获取bean
单例的话每次获取的bean的对象都是同一个
多例的话相当于只是将类的类型交给Spring管理,每次请求的bean都是不一样的对象
Spring中默认是单例,节省内存
-
@component注解和@configuration注解区别
① @component注解注释的类交给spring管理是普通的bean(animal,dog),如果再从IOC容器中获取animal对象,这时获取的就是普通的animal对象。调用dog方法获取的就是普通的dog对象,不同与IOC容器的dog对象
② 而@configuration注解注释的类,交给spring管理是cglib生成的代理类对象(这里指Animal对象),因此获取这个代理类对象(Animal)再调用相关方法(这里指dog方法),获取的都是同一个bean,实现了单例
proxyBeanMethods属性可控制代理类的开关
-
一般开发中推荐使用@configuration注解,交给spring容器管理的都是单例配置对象(针对animal这种配置类bean),节省内存
推荐看代码理解
-
总结:
与工厂bean中的单例方法区别:
工厂bean中如果单例方法返回true,那么这个bean交给IOC容器单例管理,不用区分@component注解;
而如果要获取这个工厂类,就要根据两个注解区别,来区别交给IOC容器的模式,并且工厂类不会交给spring容器管理
方式四:使用@Import注解注入bean
用处:@import注解常用来导入配置类和第三方class文件,降低了源代码与Spring技术的耦合度
用该注解的时候与其他注解结合情况:
@Import放入容器的话是单例的,但是如果放入容器的配置类不加@configuration注解,那么通过方法获取到的不是和容器中同一个对象,加了@configuration注解得到和容器中一样。
比如@import是
Animal.class
,如果Animal有@configuration注解,那么调用其中的dog方法获取的是同一个bean
方式五:编程形式注册bean
- 必须为annotationConfigApplicationContext类声明,不能用ApplicationContext,ApplicationContext没有相应的注册方法
方式六:导入实现了ImportSelector接口的类⭐️
使用条件:
@Import(ImportSelector.class)
,元数据标识位置是使用@Import且调用选择器的类根据元数据的判定,来把实现类中返回的Class名称都定义为bean。
- 源码常用
- 功能:各种条件的判定,判定完之后才决定加载不加载bean
- 属性注释:metadata是元数据,对原位置的描述信息
方式七:导入实现了ImportBeanDefinitionRegistrar接口的类
也是实现接口才能注册,然后也是使用@Import注解导入注册bean
接口内实现方法的流程:
①先根据元数据判断
②生成BeanDefinition对象,也就是要注册的bean
③使用注册器注册bean对象
-
这一个方式相对于方式六,开放了
bean的注册方式
,权限更大了
方式八:导入实现了BeanDefinitionRegistryPostProcessor接口的类
还是用@Import注解导入
作用:
BeanDefinitionRegistryPostProcessor(注册完之后最后修改),解决优先级问题,高于6.7,低于5最终注册
实现对导入的bean的覆盖的最终裁定,就是说如果有多个重复的bean加载,实现了PostProcessor的实现类中的bean中配置,会做到
优先覆盖加载
总结:
1.2 bean的加载控制
对bean进行选择配置,有选择的配置bean
- 基于编程式控制
前面import之后的加载方式都能进行选择配置
-
用
Class.forName
方法来进行控制类的加载
- 基于注解管理bean⭐️
介绍:@conditional等子注解用来决定是否配置bean
使用:@conditional注解Spring里面提供,但是需要自己写实现类实现接口。
Springboot扩展了@condition注解,有子注解使用,用的时候直接导入Springboot包即可
配置位置:有配置为bean对象的注解的地方都可以加@Conditional条件判断
-
IOC容器中有指定bean才加载
类路径下有该Class才加载
- 控制作用:省去无用的配置依赖,spring管理资源更加高效准确
1.3 bean的依赖属性配置管理⭐️(自动配置类的开发)
本节重点要掌握一种思想,约定大于配置(实现解耦功能)
约定大于配置思想:(
业务类
+配置类
)没有配置就用默认,有配置就根据属性类导入配置属性进行加载
举例:比如
猫和老鼠动画片类
的属性中,如果配置类有值,那么用配置类的,否则用约定好的(默认的)业务类和主体bean分开,使用@Import注解实现,如果需要业务类的时候加载为bean
-
本节实现步骤:
-
用业务类中的时候,导入@import类
-
将业务功能bean运行需要的资源抽取成独立的属性类(******Properties),设置读取配置文件信息,
实现解耦
-
定义业务功能bean,通常使用**@Import导入,解耦强制加载bean**,增强spring管控bean的能力
-
示例代码:
启动类:
业务类
配置类
-
总结:
-
1.4 自动配置原理⭐️
-
概述:
技术集A类似前面的业务类,设置类B类似前面的配置类,pom.xml坐标类似@Import注解
第7步开放覆盖接口对应
application.yml
-
具体注解作用:
源码重点:
- 只需要关注自动配置部分
- 分为
两部分
讲解,技术集A的加载和设置集B的加载
-
@SpringBootApplication -->@EnableAutoConfiguration -->@AutoConfigurationPackage --> @Import(AutoConfigurationPackages.Registrar.class)
设置当前配置包作为配置包(“com.itiheima”),后序会对当前包进行扫描
-
@SpringBootApplication -->@EnableAutoConfiguration -->@Import(AutoConfigurationImportSelector.class)✏️
①凡是以
aware(发现)
结尾的接口,作用就是让当前类拥有一个aware前缀的对象,如applicationContextAware接口,一个bean实现了此接口,就可以在该bean中发现applicationContext
对象并使用。(简单来说实现aware接口,该类(必须是bean)中就能发现该资源并使用)举例:这样原本只能在启动类中获取applicationContext的操作,现在变成在bean类中也能发现该对象并使用
②Inordered接口:实现了此接口,就赋予该加载的bean的优先级,这样如果第51个bean依赖第37个bean,那么根据优先级回先去加载第37个bean。
每个具有功能的bean实现了这个接口,如这个
AutoConfigurationImportSelector.class
中优先级为最大优先级(枚举) - 1③DeferredImportSelector接口:延迟选择器接口,里面有一个Group接口,Group接口里面有一个process方法,process方法实现了加载技术集A的配置,调试的话
process方法打断点
process方法里面的
getAutoConfigurationEntry()
才是加载核心方法,加载spring.fatories文件(这里会默认加载技术集A(spring-boot-autoconfigure-2.6.11.jar/META-INF/spring.fatories
))中的技术,都是以autoConfig结尾)到这一步实现了技术集A的加载,下面讲讲设置集B的加载
spring.fatories文件在autoconfigure配置里面
对应第六、七步,将
设置集
B默认加载
这里以Redis技术集的加载为例
xxxConfiguration.class --> xxxProperties.class --> application.yml
spring.fatories文件里的配置信息为xxxConfiguration,现在以RedisAutoConfiguration
(默认技术集A中有的东西)为例拆解自动配置属性
-
@ConditionalOnClass(RedisOperations.class)
讲解衔接上一步的设置技术集A
先做条件检测,如果环境中包含某某类,
才会加载为bean
(环境检测,降低spring管理bean的负载)**举例1:**假如原来项目中是空的,但是boot默认加载技术集里面有RedisAutoConfiguration,这时候我在pom.xml文件中导入Redis相关坐标,这时候RedisAutoConfiguration中的@Conditional注解就在类路径下识别到了Redis的类,进而这个技术集合就生效了
**举例2:**RedisAutoConfiguration相当于之前的动画片业务类,而
@EnableConfigurationProperties
注解所用得类相当于之前猫和老鼠的配置类 -
@EnableConfigurationProperties(RedisProperties.class)
讲解:环境满足之后,去加载配置类为bean,配置类又依赖application.yml文件,这样的话一个xxxConfiguration配置类含有配置信息bean
-
最后两种方法的讲解:
将对Redis的对外操作对象配置为bean,当技术中有需要配置条件的时候,直接去IOC容器中获取即可
-
总结
-
springboot启动时先加载spring.factories文件中的①org.springframework.boot.autoconfigure.EnableAutoConfiguration配置项,将其中配置所有以
xxxAutoConfiguration.class
类加载为配置类(这就是加载默认的技术集A)。②要注意这些只是默认技术的配置类,而不是这些技术本身。根据这些配置类的@Conditional注解和pom.xml坐标(jar包)来决定加载具体的技术
-
在加载bean的时候,bean对应的类定义上都设置有加载条件,因此有可能加载成功,也可能条件检测失败不加载bean
-
对于可以正常加载成bean的类,通常会通过@EnableConfigurationProperties注解初始化对应的配置属性类并加载对应的配置
-
配置属性类上通常会通过@ConfigurationProperties加载指定前缀的配置,当然这些配置通常都有默认值。如果没有默认值,就强制你必须配置后使用了
-
一句话总结:
就是springboot加载的时候会全部加载配置类,如果检测到环境中存在环境,那么把配置类交给bean管理,实现自动配置,减轻spring的bean管控负载
1.5 变更自动配置✏️
这个问题是自定义starter的基础
问题:
- boot中自定义了一些技术集,但是如果是后来的技术如何装进boot让其自动装配呢?
解决:
仿照mybatisplus-starter,可以自己在
META-INF目录下
写一个自己的spring.factories
文件,将自己的业务配置类信息加入到这个文件中,最后加入自身jar包坐标
,这样boot在启动的时候会扫描全部的spring.factories文件然后扫描到mp的文件时会自动加上这个技术集
自己自定义:
- 自己在
META-INF目录下
写一个自己的spring.factories
,这样boot在启动的时候会自动加载自己在配置文件spring.factories
配置的业务类自定义自动配置 + 控制boot自动配置类的加载(根上解决依赖)
排除坐标(只是没有实现,但是配置类还自动加载(默认技术集中的坐标))
总结:
从上述配置中可以看出,自动配置也是加载bean的方式
- 唯一目的:
帮开发者实现自定义自动配置类,管控bean的注入
2.自定义starter开发
自动配置学习完后,我们就可以基于自动配置的特性,开发springboot技术中最引以为傲的功能了,starter。其实通过前期学习,我们发现用什么技术直接导入对应的starter,然后就实现了springboot整合对应技术,再加上一些简单的配置,就可以直接使用了。这种设计方式对开发者非常友好,本章就通过一个案例的制作,开发自定义starter来实现自定义功能的快捷添加。
自定义starter就是设置一组bean,然后通过boot的自动配置实现导入该坐标就能自启相关功能
大体开发版块:
- 自动配置相关
- 自动配置类(xxxConfig.java)
- 自动配置需要加载文件(spring.factories)
- 业务相关
- 业务类
YL-2-1.案例:记录系统访客独立IP访问次数
本案例的功能是统计网站独立IP访问次数的功能,并将访问信息在后台持续输出。整体功能是在后台每10秒输出一次监控信息(格式:IP+访问次数) ,当用户访问网站时,对用户的访问行为进行统计。
例如:张三访问网站功能15次,IP地址:192.168.0.135,李四访问网站功能20次,IP地址:61.129.65.248。那么在网站后台就输出如下监控信息,此信息每10秒刷新一次。
IP访问监控
+-----ip-address-----+--num--+
| 192.168.0.135 | 15 |
| 61.129.65.248 | 20 |
+--------------------+-------+
在进行具体制作之前,先对功能做具体的分析
-
数据记录在什么位置
最终记录的数据是一个字符串(IP地址)对应一个数字(访问次数),此处可以选择的数据存储模型可以使用java提供的map模型,也就是key-value的键值对模型,或者具有key-value键值对模型的存储技术,例如redis技术。本案例使用map作为实现方案,有兴趣的小伙伴可以使用redis作为解决方案。
-
统计功能运行位置,因为每次web请求都需要进行统计,因此使用拦截器会是比较好的方案,本案例使用拦截器来实现。不过在制作初期,先使用调用的形式进行测试,等功能完成了,再改成拦截器的实现方案。
-
为了提升统计数据展示的灵活度,为统计功能添加配置项。输出频度,输出的数据格式,统计数据的显示模式均可以通过配置实现调整。
- 输出频度,默认10秒
- 数据特征:累计数据 / 阶段数据,默认累计数据
- 输出格式:详细模式 / 极简模式
在下面的制作中,分成若干个步骤实现。先完成最基本的统计功能的制作,然后开发出统计报表,接下来把所有的配置都设置好,最后将拦截器功能实现,整体功能就做完了。
YL-2-2.IP计数业务功能开发(自定义starter)
- 可以看p162理解一下开发规范
原理:就是将设置一个spring.factories文件和自动配置类,让bean随着boot工程加载而加载,顺便导入所有的bean
名称规范:spring官方starter
spring-xxx-starter
,自定义技术名-spring-boot-starter
安装步骤:
- 将制作好的starter先安装到maven仓库中
- 导入相关坐标,使用starter
本功能最终要实现的效果是在现有的项目中导入一个starter,对应的功能就添加上了,删除掉对应的starter,功能就消失了,要求功能要与原始项目完全解耦。因此需要开发一个独立的模块,制作对应功能。
步骤一:创建全新的模块,定义业务功能类
功能类的制作并不复杂,定义一个业务类,声明一个Map对象,用于记录ip访问次数,key是ip地址,value是访问次数
public class IpCountService {
private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();
}
有些小伙伴可能会有疑问,不设置成静态的,如何在每次请求时进行数据共享呢?记得,当前类加载成bean以后是一个单例对象,对象都是单例的,哪里存在多个对象共享变量的问题。
步骤二:制作统计功能
制作统计操作对应的方法,每次访问后对应ip的记录次数+1。需要分情况处理,如果当前没有对应ip的数据,新增一条数据,否则就修改对应key的值+1即可
public class IpCountService {
private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();
public void count(){
//每次调用当前操作,就记录当前访问的IP,然后累加访问次数
//1.获取当前操作的IP地址
String ip = null;
//2.根据IP地址从Map取值,并递增
Integer count = ipCountMap.get(ip);
if(count == null){
//如果为null就将ip放进去
ipCountMap.put(ip,1);
}else{
ipCountMap.put(ip,count + 1);
}
}
}
通过HttpServletRequest对象,获取ip地址
public class IpCountService {
private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();
@Autowired
//当前的request对象的注入工作由使用当前starter的工程提供自动装配
private HttpServletRequest httpServletRequest;
public void count(){
//每次调用当前操作,就记录当前访问的IP,然后累加访问次数
//1.获取当前操作的IP地址
String ip = httpServletRequest.getRemoteAddr();
//2.根据IP地址从Map取值,并递增
Integer count = ipCountMap.get(ip);
if(count == null){
ipCountMap.put(ip,1);
}else{
ipCountMap.put(ip,count + 1);
}
}
}
步骤三:定义自动配置类⭐️
我们需要做到的效果是导入当前模块即开启此功能,因此使用自动配置实现功能的自动装载,需要开发自动配置类在启动项目时加载当前功能。
public class IpAutoConfiguration {
@Bean
public IpCountService ipCountService(){
return new IpCountService();
}
}
自动配置类需要在spring.factories文件中做配置方可自动运行。
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.itcast.autoconfig.IpAutoConfiguration
步骤四:在原始项目中模拟调用,测试功能
原始调用项目中导入当前开发的starter
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>ip_spring_boot_starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
温馨提示
由于当前制作的功能需要在对应的调用位置进行坐标导入,因此必须保障仓库中具有当前开发的功能,所以每次原始代码修改后,需要重新编译并安装到仓库中。为防止问题出现,建议每次安装之前先clean然后install,保障资源进行了更新。切记切记!!
当前效果
在分页controller方法中使用这个IpCountService.count方法,每次调用分页操作后,可以在控制台输出当前访问的IP地址,此功能可以在count操作中添加日志或者输出语句进行测试。
YL-2-3.定时任务报表开发
定时功能实现:
- 可以选取第三方技术Quartz实现,也可以选择Spring内置的task来完成此功能,此处选用Spring的task作为实现方案。
- 定时功能是一种监控功能,时刻监控该工程
步骤一:开启定时任务功能
步骤二:制作显示统计数据功能
定义显示统计功能的操作print(),并设置定时任务,当前设置每10秒运行一次统计数据。
String.format()
就是占位符的功能
当前效果
每次调用分页操作后,可以在控制台看到统计数据,到此基础功能已经开发完毕。
YL-2-4.使用属性配置设置功能参数
学配置类的设置与使用
由于当前报表显示的信息格式固定,为提高报表信息显示的灵活性,需要通过yml文件设置参数,控制报表的显示格式。
步骤一:定义参数格式
设置3个属性,分别用来控制显示周期(cycle),阶段数据是否清空(cycleReset),数据显示格式(model)
步骤二:定义封装参数的属性类,读取配置参数
为防止项目组定义的参数种类过多,产生冲突,通常设置属性前缀会至少使用两级属性作为前缀
进行区分。
日志输出模式是在若干个类别选项中选择某一项,对于此种分类性数据建议制作枚举定义分类数据
,当然使用字符串也可以。
步骤三:加载属性类
步骤四:应用配置属性
在应用配置属性的功能类中,使用自动装配加载对应的配置bean,然后使用配置信息做分支处理。
注意:清除数据的功能一定要在输出后运行,否则每次查阅的数据均为空白数据。
当前效果
在web程序端可以通过控制yml文件中的配置参数对统计信息进行格式控制。但是数据显示周期还未进行控制。
YL-2-5.使用属性配置设置定时器参数
无论是什么地方,读取配置文件都应该想法从配置类对象中读取,否则是一种不规范行为
问题:
读取属性值应该是从bean中读取(规范),但是这个用
@EnableConfigurationProperties(xxx.class)
生成的配置bean不规范,从而导致读取属性难以读取解决问题:
①@EnableConfigurationProperties不能指定名称,导致@Scheduled注解上无法识别Java对象属性
②去掉
@EnableConfigurationProperties
注解,在配置类上设置@Component注解,在启动类上用@Import注解或@ComponentScan导入注册为bean
在使用属性配置中的显示周期数据时,遇到了一些问题。由于无法在@Scheduled注解上直接使用配置数据,改用曲线救国的方针,放弃使用@EnableConfigurationProperties注解对应的功能,改成最原始的bean定义格式。
步骤一:@Scheduled注解使用#{}读取bean属性值
此处读取bean名称为ipProperties的bean的cycle属性值
@Scheduled(cron = "0/#{ipProperties.cycle} * * * * ?")
public void print(){
}
步骤二:属性类定义bean并指定bean的访问名称
如果此处不设置bean的访问名称,spring会使用自己的命名生成器生成bean的长名称,无法实现属性的读取
@Component("ipProperties")
@ConfigurationProperties(prefix = "tools.ip")
public class IpProperties {
}
步骤三:弃用@EnableConfigurationProperties注解对应的功能,改为导入bean的形式加载配置属性类
@EnableScheduling
//@EnableConfigurationProperties(IpProperties.class)
@Import(IpProperties.class)
public class IpAutoConfiguration {
@Bean
public IpCountService ipCountService(){
return new IpCountService();
}
}
当前效果
在web程序端可以通过控制yml文件中的配置参数对统计信息的显示周期进行控制
YL-2-6.拦截器开发
功能:
- 就是在starter中配置了一个web拦截器功能实现类,记得要加入总配置类中,这样就实现了每次导入starter,就自动加上了web拦截器
具体实现:
- 继承Servlet的拦截器接口,实现相应的拦截功能
- 实现
WebConfigure
的接口,将拦截器
注册进web容器- 在主项目中导入这个starter的
MVCConfig
配置类
步骤一:开发拦截器
使用自动装配加载统计功能的业务类,并在拦截器中调用对应功能
步骤二:配置拦截器
配置mvc拦截器,设置拦截对应的请求路径。此处拦截所有请求,用户可以根据使用需要设置要拦截的请求。甚至可以在此处加载IpCountProperties中的属性,通过配置设置拦截器拦截的请求。
当前效果
在web程序端导入对应的starter后功能开启,去掉坐标后功能消失,实现自定义starter的效果。
到此当前案例全部完成,自定义stater的开发其实在第一轮开发中就已经完成了,就是创建独立模块导出独立功能,需要使用的位置导入对应的starter即可。如果是在企业中开发,记得不仅需要将开发完成的starter模块install到自己的本地仓库中,开发完毕后还要deploy到私服上,否则别人就无法使用了。
YL-2-7.功能性完善——开启yml提示功能
开启yml配置提示
步骤:
在配置类写好文档注释
导入相应jar包,编译之后得到配置提示的json文件放入
META-INF
目录下就可以了
我们在使用springboot的配置属性时,都可以看到提示,尤其是导入了对应的starter后,也会有对应的提示信息出现。但是现在我们的starter没有对应的提示功能,这种设定就非常的不友好,本节解决自定义starter功能如何开启配置提示的问题。
springboot提供有专用的工具实现此功能,仅需要导入下列坐标。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
程序编译后,在META-INF目录中会生成对应的提示文件,然后拷贝生成出的文件到自己开发的META-INF目录中,并对其进行编辑。打开生成的文件,可以看到如下信息。其中groups属性定义了当前配置的提示信息总体描述,当前配置属于哪一个属性封装类,properties属性描述了当前配置中每一个属性的具体设置,包含名称、类型、描述、默认值等信息。hints属性默认是空白的,没有进行设置。hints属性可以参考springboot源码中的制作,设置当前属性封装类专用的提示信息,下例中为日志输出模式属性model设置了两种可选提示信息。
{
"groups": [
{
"name": "tools.ip",
"type": "cn.itcast.properties.IpProperties",
"sourceType": "cn.itcast.properties.IpProperties"
}
],
"properties": [
{
"name": "tools.ip.cycle",
"type": "java.lang.Long",
"description": "日志显示周期",
"sourceType": "cn.itcast.properties.IpProperties",
"defaultValue": 5
},
{
"name": "tools.ip.cycle-reset",
"type": "java.lang.Boolean",
"description": "是否周期内重置数据",
"sourceType": "cn.itcast.properties.IpProperties",
"defaultValue": false
},
{
"name": "tools.ip.model",
"type": "java.lang.String",
"description": "日志输出模式 detail:详细模式 simple:极简模式",
"sourceType": "cn.itcast.properties.IpProperties"
}
],
// 提示
"hints": [
{
"name": "tools.ip.model",
"values": [
{
"value": "detail",
"description": "详细模式."
},
{
"value": "simple",
"description": "极简模式."
}
]
}
]
}
总结
- 自定义starter其实就是做一个独立的功能模块,核心技术是利用自动配置的效果在加载模块后加载对应的功能
- 通常会为自定义starter的自动配置功能添加足够的条件控制,而不会做成100%加载对功能的效果
- 本例中使用map保存数据,如果换用redis方案,在starter开发模块中就要导入redis对应的starter
- 对于配置属性务必开启提示功能,否则使用者无法感知配置应该如何书写
3.springboot启动流程
掌握springboot如何加载容器,如何配置容器流程,只是加速Spring开发
核心:还是加载spring容器,
ApplicationContext
- springboot启动流程 = 初始化数据 + 加载容器
boot启动流程就两步
new Application(primarySource)
:加载配置信息,初始化各种配置对象,还没初始化容器new Application(primarySource).run(args)
:初始化IOC容器
其实不管是springboot程序还是spring程序,**启动过程本质上都是在做容器的初始化,并将对应的bean初始化出来放入容器。**在spring环境中,每个bean的初始化都要开发者自己添加设置,但是切换成springboot程序后,自动配置功能的添加帮助开发者提前预设了很多bean的初始化过程,加上各种各样的参数设置,使得整体初始化过程显得略微复杂,但是核心本质还是在做一件事,初始化容器。作为开发者只要搞清楚springboot提供了哪些参数设置的环节,同时初始化容器的过程中都做了哪些事情就行了。
springboot初始化的参数根据参数的提供方,划分成如下3个大类,每个大类的参数又被封装了各种各样的对象,具体如下:
- 环境属性(Environment)
- 系统配置(spring.factories)
- 参数(Arguments、application.properties)
上述过程描述了springboot程序启动过程中做的所有的事情,这个时候好奇宝宝们就会提出一个问题。如果想干预springboot的启动过程,比如自定义一个数据库环境检测的程序,该如何将这个过程加入springboot的启动流程呢?
遇到这样的问题,大部分技术是这样设计的,设计若干个标准接口,对应程序中的所有标准过程。当你想干预某个过程时,实现接口就行了。例如spring技术中bean的生命周期管理就是采用标准接口进行的。
public class Abc implements InitializingBean, DisposableBean {
public void destroy() throws Exception {
//销毁操作
}
public void afterPropertiesSet() throws Exception {
//初始化操作
}
}
springboot启动过程由于存在着大量的过程阶段,如果设计接口就要设计十余个标准接口,这样对开发者不友好,同时整体过程管理分散,十余个过程各自为政,管理难度大,过程过于松散。那springboot如何解决这个问题呢?它采用了一种最原始的设计模式来解决这个问题,这就是监听器模式,使用监听器来解决这个问题。
springboot将自身的启动过程比喻成一个大的事件,该事件是由若干个小的事件组成的。例如:
- org.springframework.boot.context.event.ApplicationStartingEvent
- 应用启动事件,在应用运行但未进行任何处理时,将发送 ApplicationStartingEvent
- org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent
- 环境准备事件,当Environment被使用,且上下文创建之前,将发送 ApplicationEnvironmentPreparedEvent
- org.springframework.boot.context.event.ApplicationContextInitializedEvent
- 上下文初始化事件
- org.springframework.boot.context.event.ApplicationPreparedEvent
- 应用准备事件,在开始刷新之前,bean定义被加载之后发送 ApplicationPreparedEvent
- org.springframework.context.event.ContextRefreshedEvent
- 上下文刷新事件
- org.springframework.boot.context.event.ApplicationStartedEvent
- 应用启动完成事件,在上下文刷新之后且所有的应用和命令行运行器被调用之前发送 ApplicationStartedEvent
- org.springframework.boot.context.event.ApplicationReadyEvent
- 应用准备就绪事件,在应用程序和命令行运行器被调用之后,将发出 ApplicationReadyEvent,用于通知应用已经准备处理请求
- org.springframework.context.event.ContextClosedEvent(上下文关闭事件,对应容器关闭)
上述列出的仅仅是部分事件,当应用启动后走到某一个过程点时,监听器监听到某个事件触发,就会执行对应的事件。除了系统内置的事件处理,用户还可以根据需要自定义开发当前事件触发时要做的其他动作。
//设定监听器,在应用启动开始事件时进行功能追加
public class MyListener implements ApplicationListener<ApplicationStartingEvent> {
public void onApplicationEvent(ApplicationStartingEvent event) {
//自定义事件处理逻辑
}
}
按照上述方案处理,用户就可以干预springboot启动过程的所有工作节点,设置自己的业务系统中独有的功能点。
总结
- springboot启动流程是先初始化容器需要的各种配置,并加载成各种对象,初始化容器时读取这些对象,创建容器
- 整体流程采用事件监听的机制进行过程控制,开发者可以根据需要自行扩展,添加对应的监听器绑定具体事件,就可以在事件触发位置执行开发者的业务代码
监听器
工作机制:
监听器工作模式,可以实现干预boot启动过程,想在boot启动时期哪干预就干预
springboot给开发者留了一个Listener接口,如果开发者实现了这个接口(不加泛型的话),那么这个监听器就会在boot启动过程中多个事件都运行
如果实现了带泛型的接口,那么只会在boot执行到特定阶段的时候运行
- 文章学习链接
(上下文关闭事件,对应容器关闭)
上述列出的仅仅是部分事件,当应用启动后走到某一个过程点时,监听器监听到某个事件触发,就会执行对应的事件。除了系统内置的事件处理,用户还可以根据需要自定义开发当前事件触发时要做的其他动作。
//设定监听器,在应用启动开始事件时进行功能追加
public class MyListener implements ApplicationListener<ApplicationStartingEvent> {
public void onApplicationEvent(ApplicationStartingEvent event) {
//自定义事件处理逻辑
}
}
按照上述方案处理,用户就可以干预springboot启动过程的所有工作节点,设置自己的业务系统中独有的功能点。
总结
- springboot启动流程是先初始化容器需要的各种配置,并加载成各种对象,初始化容器时读取这些对象,创建容器
- 整体流程采用事件监听的机制进行过程控制,开发者可以根据需要自行扩展,添加对应的监听器绑定具体事件,就可以在事件触发位置执行开发者的业务代码
监听器
工作机制:
监听器工作模式,可以实现干预boot启动过程,想在boot启动时期哪干预就干预
springboot给开发者留了一个Listener接口,如果开发者实现了这个接口(不加泛型的话),那么这个监听器就会在boot启动过程中多个事件都运行
如果实现了带泛型的接口,那么只会在boot执行到特定阶段的时候运行
[外链图片转存中…(img-XQWL1Ehw-1688197404544)]
[外链图片转存中…(img-SMaCelwL-1688197404545)]
[外链图片转存中…(img-msgJPKMO-1688197404546)]
[外链图片转存中…(img-ojyajChT-1688197404547)]
- 文章学习链接