0
点赞
收藏
分享

微信扫一扫

【原理篇】三、SpringBoot自动配置原理

敬亭阁主 2023-11-08 阅读 31

文章目录

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();
	}
}

在这里插入图片描述

此时,配置都硬编码了,显然不合理。

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配置绑死了,没配置,类都受影响。

@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

//@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
@EnableConfigurationPropertiesCartoonProperties.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")
@EnableConfigurationPropertiesCartoonProperties.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........
	}
}
举报

相关推荐

0 条评论