文章目录
- 0、背景demo
- 1、自动配置思路
- 2、META-INF/spring.factories
- 3、Redis自动配置
- 4、自定义一个自动配置
- 5、排除SpringBoot内置自动配置类的加载
- 6、补充点:ApplicationContextAware接口
0、背景demo
用一个循序渐进的示例来体验属性配置,方便后面理解自动配置,先准备几个demo类:
@Data
public class Cat{
private String name;
private Integer age;
}
@Data
public class Mouse{
private String name;
private Integer age;
}
//猫和老鼠卡通类
@Component
public class CartoonCatAndMouse{
private Cat cat;
private Mouse mouse;
//提供个构造方法给两个属性赋值,不然默认null,下面会空指针
public CartoonCatAndMouse(){
cat = new Cat();
cat.setName("tom");
cat.setAge(3);
mouse= new Mouse();
mouse.setName("jerry");
mouse.setAge(3);
}
public void play(){
System.out.println(cat.getAge()+"岁的"+cat.getName()+"与"+mouse.getAge()+"岁的"+mouse.getName()+"打起来了");
}
}
//启动类中调用下play方法
@SpringBootApplication
public class App{
public static void main(String[] args){
ConfigurableApplicationContext ctx = SringApplication.run(app.class);
CartoonCatAndMouse bean = ctx.getBean(CartoonCatAndMouse.class);
bean.play();
}
}
此时,配置都硬编码了,显然不合理。
优化第一步:引入yaml配置并@ConfigurationProperties读取
cartoon:
cat:
name: tom
age: 3
mouse:
name: jerry
age: 4
@Component
@ConfigurationProperties(prefix = "cartoon")
//加个set,不然cat属性和mouse属性为null
@Data
public class CartoonCatAndMouse{
private Cat cat;
private Mouse mouse;
public void play(){
System.out.println(cat.getAge()+"岁的"+cat.getName()+"与"+mouse.getAge()+"岁的"+mouse.getName()+"打起来了");
}
}
此时,如果yaml配置中没有相关配置,则对应的对象为null,进而空指针。也就是说,一加@ConfigurationProperties(prefix = “cartoon”),我的类和yaml配置绑死了,没配置,类都受影响。
优化第二步:引入独立配置类CartoonProperties
@ConfigurationProperties(prefix = "cartoon")
@Data
//旧知识点,读取yaml的类的对象必须受Spring容器管控,否则,即使拿到yaml值也无法set给你
@Component
public class CartoonProperties {
private Cat cat;
private Mouse mouse;
}
@Component
@Data
public class CartoonCatAndMouse{
private Cat cat;
private Mouse mouse;
//构造器注入了,用@Autowired也行
private CartoonProperties cartoonProperties;
public void play(CartoonProperties cartoonProperties){
this.cartoonProperties = cartoonProperties;
cat = new Cat();
cat.setName(cartoonProperties.getCat()!=null && StringUtils.hasText(cartoonProperties.getCat().getName())
?cartoonProperties.getCat().getName():"tom"); //有则用,无则用默认值
//Mouse对象同样写法,略
System.out.println(cat.getAge()+"岁的"+cat.getName()+"与"+mouse.getAge()+"岁的"+mouse.getName()+"打起来了");
}
}
此时,有个缺点,CartoonProperties类不管用不用,都被强制加载成一个Bean了,但去掉@Component,只留@ConfigurationProperties语法错误 ⇒ 用@EnableConfigurationProperties
优化第三步:@EnableConfigurationProperties,改掉强制加载Bean
//@Component 不再需要定义为Bean
@ConfigurationProperties(prefix = "cartoon")
@Data
public class CartoonProperties {
private Cat cat;
private Mouse mouse;
}
@ConfigurationProperties和@EnableConfigurationProperties,前者是做属性绑定的,后者是开启属性绑定,并设定对应的目标是谁
@Component //这个Component也可以去掉,用的时候@Import(CartoonCatAndMouse.class)
@Data
//即当我加载CartoonCatAndMouse时,就用CartoonProperties.class,并把CartoonProperties类加载成Bean
@EnableConfigurationProperties(CartoonProperties.class)
public class CartoonCatAndMouse{
private Cat cat;
private Mouse mouse;
//重复代码,略....
这套模式的亮点有以下几个:
- 合理的加载配置文件,即你配置了就用你的,没配就用默认值来工作
- 对于属性类xxxProperties不用强制配置成Bean,使用@EnableConfigurationProperties
- 对于业务功能的Bean,通常使用@Import将一个类加载成Bean,来解耦强制加载Bean,以降低Spring容器管理Bean的工作量以及强度
1、自动配置思路
- 收集Spring开发者的编程习惯,得到常用技术集列表 ⇒ 技术集A
- 收集每个技术的常用参数值,得到常用配置值列表 ⇒ 设置集B
- 初始化你项目的SpringBoot基础环境,包括加载用户自定义的Bean以及用户导入的其他坐标,得到初始化环境
- 把技术集A中具有使用条件的技术设置为按条件加载,与初始化环境对比,如有Redis核心类时,就触发加载Redis技术集的资源
- 约定大于配置,设置集B里的参数值做为默认配置加载
- 对于要修改的配置,开发者自行覆盖
总之就是,SpringBoot官方整理了常用的技术以及对应的配置值,可根据你项目的环境来自动加载相关的Bean,并给你默认配置,减少开发者的工作量。
2、META-INF/spring.factories
从启动类的@SpringBootApplication注解开始看:
关键点:
- @Import(AutoConfigurationPackages.Registrar.class) ,debug可发现,这里是设置当前配置所在的包为扫描包,后续要针对当前包进行扫描
- @Import(AutoConfigurationImportSelector.class)
继续往下看AutoConfigurationImportSelector,它实现了很多接口,可分三类:
- DeferredImportSelector文末补充点一节
- Ordered:有关Bean的创建顺序
重写的DeferredImportSelector接口中的process方法中除去断言代码,往下debug看getAutoConfigurationEntry方法:
调用了获取候选配置的方法getCandidateConfigurations:
跳过断言代码,看到核心肯定在loadFactoryNames方法:
继续debug方法loadFactoryNames,除去判空,关键的在loadSpringFactories方法:
可以看到loadSpringFactories方法在读META-INF/spring.factories里的资源,这里就是核心了。
3、Redis自动配置
查看SpringBootAutoConfiguration下的spring.factories文件:
以Redis的自动装配为例来看:spring.factories文件钟找到了RedisAutoConfiguration类,即Redis的自动配置类:
对比前面刚开始的背景案例,这个Redis的自动装配就很明晰了:
- @ConditionOnClass是Redis相关Bean加载的条件,引入Redis起步依赖后,里面包含这里的RedisOperations.class类,也就满足了加载条件
- @EnableConfigurationProperties后面是属性配置类
- @Import导入两个Redis底层的Bean
- 定义两个Redis客户端操作的Template的Bean,并前提是用户没有自定义这个Bean
这就是前面提到的,SpringBoot加了无数技术的自动配置类,用对应的条件来检测你当前Spring项目中要不要加对应的技术的Bean等资源。
4、自定义一个自动配置
对比上面的Redis自动配置类和开篇的demo,前面的CartoonCatAndMouse类就是RedisAutoConfiguration,CartoonProperties就是RedisProperties,那就模仿官方写法,把CartoonCatAndMouse改成一个真正的自动配置类。新建META-INF/spring.factories
,内容:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.plat.bean.CartoonCatAndMouse
此时,CartooCatAndMouse就完成了自动配置,修一下,加上加载条件:
@Data
@ConditionalOnClass(name = "com.plat.core.MyCore.class")
@EnableConfigurationProperties(CartoonProperties.class)
public class CartoonCatAndMouse{
private Cat cat;
private Mouse mouse;
//重复代码,略....
此时,项目启动,有MyCore.class类被加载时,触发自动装配,完成相关Bean的加载。最后,如果直接把一个类配置到spring.factories文件中,能从IoC容器中get到这个Bean吗? ==> 可以,但属性都为null
5、排除SpringBoot内置自动配置类的加载
截至SpringBoot2.6版本,spring.factories中已有130多种技术对应的自动配置类,没必要启动时去全部判断一次,可以直接排除掉一些你肯定不用的自动配置类。
- 方法一:配置文件
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
- org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
- 方法二:注解
@SpringBootApplication(excludeName = "",exclude = {})
//exclude属性其实是@EnableAutoConfiguration注解的,但它被@SpringBootApplication包含,属性也被拿了过来
- 方式三:排除掉依赖,直接通过干涉激活条件@Conditional实现,如去除tomcat自动配置(条件激活),添加jetty自动配置(条件激活)
6、补充点:ApplicationContextAware接口
获取ApplicationContext对象,可以通过实现ApplicationContextAware接口:
@Component
public class MyIocUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext){
this.applicationContext = applicationContext; //给当前工具类的applicationContext属性赋值
}
public static ApplicationContext getApplicationContext(){
return applicationContext;
}
}
此时,在你需要使用上下文对象的地方直接:
MyIocUtils.getApplicationContext()
即可拿到上下文对象。当然你不封装成工具类也行,直接在你需要用上下文对象的类里写:
public class YourClass{
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext){
this.applicationContext = applicationContext; //给当前类的applicationContext属性赋值
}
public void doSome(){
//使用上下文对象
applicationContext.getBean........
}
}