SpringBoot自动装配原理

443 阅读7分钟

@SpringBootApplication

这个注解相当于

@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan合起来的效果, 为SpringBoot标注程序入口

@Configuration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    	@AliasFor(annotation = Component.class)
	String value() default "";
    	boolean proxyBeanMethods() default true; //修改组件中的内容是单实例的还是原型的,设置为false 则从组件中取出的内容每次都是一个新的
    	boolean enforceUniqueMethods() default true;
}

proxyBeanMethods的优点缺点:

  • 设置为true,则每次从容器中取出的值,都是相等的,一般用于有组件依赖的情况, 单实例每次在springboot使用这些组件的时候都会去容器中检查有没有已经存在的会消耗资源, FULL模式
  • 设置为false SPringboot就不会再去检查容器中有没有已经存在的组件了,运行效率较快 Lite模式

如果两个组件没有明显的依赖关系,那么推荐将该属性设置为false

@Import

给容器中导入一个组件

使用场景:写在任何一个配置类里或者是组件里面都可以 ,@Import 要写在 组件上方 可以是@Configuration,也可以是@Controller

//在MyConfig 配置类中导入两个组件, 在Config 配置类中添加 User 和InfluxDbOkHttpClientBuilderProvider的实例.默认组件的名字是全类名
@Import({User.class, InfluxDbOkHttpClientBuilderProvider.class})
public class MyConfig {
    @Bean
    public User User(){
        return new User("hc",25);
    }
    @Bean
    public Pet pet(){
        return new Pet("smallOrange","cat");
    }
}

@Conditional

条件装配:满足Conditional指定的条件,则进行注入

@Conditional 为一个根注解, 他的下面派生了许多注解 ConditionalOnxxxxxx //在IDEA中可以按Ctrl+N 输入Conditional 来查看派生的注解

@ConditionalOnBean 当容器中存在某个bean的时候才会做某些事情

@ConditionalOnMissingBean 当容器中不存在某个bean的时候才会做某些事情

@ConditionalOnclass 当容器中存在某个class 的时候才会生效

@ConditionalOnMissingclass 当容器中不存在某个class 的时候才会生效

@ConditionalOnResources 当项目路径存在某个资源的时候才干什么

......

举例

@Configuration
public class MyConfig {
    //User  Bean组件依赖于pet组件
    @Bean
    public User User(){
        User hc = new User("hc", 25);
        hc.setPet(pet());
        return hc;
    }
   
    @Bean
    public Pet pet(){
        return new Pet("smallOrange","cat");
    }
}
@SpringBootApplication()
public class MyApplication  {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(MyApplication.class, args);
        System.out.println(run.containsBean("User"));
        System.out.println(run.containsBean("pet"));
    }
}

输出结果 true true,现在我们将pet的@bean 注释掉, 在User 上加入 @ConditionalOnbean 再进行测试

@Configuration
public class MyConfig {
    @Bean
    @ConditionalOnBean(name = "pet")
    public User User(){   // 注册Bean的方法
        User hc = new User("hc", 25);
        hc.setPet(pet());
        return hc;
    }
    /*@Bean*/
    public Pet pet(){
        return new Pet("smallOrange","cat");
    }
}

测试结果为false false @ConditionalOnbean 当pet bean 存在的时候才会将User 组件加载到容器中,所以结果都为false;

@Conditional 不止可以作用在注册方法上, 也可以作用在类上, 表示当某个条件是这个类生效或者不生效

@ImportResource

原生配置文件导入 使用场景:

我们在Resources 目录下写的beans.xml (Spring 组件注册文件) 在我们SPringBoot 容器中是不生效的, SpringBoot不知道这个配置文件是用来做什么, 有时候我们可能会引入别人写好的配置xml文件, 如果想使用的话 我们需要将这些xml 中注册的组件 一个个迁移到 带有@Configuration 的配置类中, 如果我们不想这样做,我们只需要在任意一个 配置类 上加入 @ImportResource("classpath:文件路径") 告诉SpringBoot这个xml 是为了注册组件

配置绑定

@ConfigurationProperties+@Component

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface ConfigurationProperties {
    @AliasFor("prefix")
    String value() default "";
    @AliasFor("value")
    String prefix() default "";
    boolean ignoreInvalidFields() default false;
    boolean ignoreUnknownFields() default true;
}

使用场景

将我们的javabean 和配置类 一一绑定

@Data//1  
@Component   //2      要想使用3 需要标注2  
@ConfigurationProperties(prefix = "Car") //3
public class Car {
    private String brand;
    private double price;
}
mycar.brand=BYD
mycar.price=1000000
@RestController
public class TestController {
    @Autowired
    private Car car;
    @RequestMapping("/car")
    public Car car(){
        return car;
    }
}

@EnableConfigurationProperties+@ConfigurationProperties

@EnableConfigurationProperties

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({EnableConfigurationPropertiesRegistrar.class})
public @interface EnableConfigurationProperties {
    String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
    Class<?>[] value() default {};
}

示例:

Myconfig

@Configuration
@EnableConfigurationProperties(Car.class)
// 1:开启Car类的配置绑定功能
//2.把这个Car 注册到容器中, 相当于在Car类上 标记@Component
public class MyConfig {
    @Bean
    @ConditionalOnBean(name = "pet")
    public User User(){
        User hc = new User("hc", 25);
        hc.setPet(pet());
        return hc;
    }
    /*@Bean*/
    public Pet pet(){
        return new Pet("smallOrange","cat");
    }
}
@Data
@ConfigurationProperties(value = "mycar")
public class Car {
    private String brand;
    private double price;
}

使用场景, 有的时候我们引入第三方的类, 这个时候我们没办法去在他的类上面标注@Component, 这个时候就需要我们使用这种标注方式来进行数据绑定

自动配置原理

引导加载自动配置类

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {}

@SpringBootConfiguration 
@Configuration 代表当前是一个配置类
@ComponentScan 包扫描工具指定扫描那些包

@EnableAutoConfiguration 重点!

@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
}

=======================================================================================

@AutoConfigurationPackage


进入@AutoConfigurationPackage,发现它导入了AutoConfigurationPackages.Registrar.class

import的作用,是将导入的类添加为容器的组件 进入AutoConfigurationPackages.Registrar.class这个类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
	String[] basePackages() default {};
	Class<?>[] basePackageClasses() default {};
}

找到了一个名为Registrar的内部类 ! 扫描组件的核心类 重点为registerBeanDefinitions方法

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}
		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}
	}

选中参数中的metadata 右键计算表达式 得到的结果为Springboot.Myapplication

SpringBoot 为我的包名, Myapplication 为启动程序的类名

register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0])); 

这一句的意思 是 将 这个包的所有组件进行扫描,然后注册

Registrar 方法的作用就是把某一个包里面的组件批量注册进 Spring容器

=======================================================================================

@AutoConfigurationImportSelector.class 初始化自动配置加载类

进入这个类 核心方法

public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    // 返回的才是最重要的
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

这个方法从哪来呢?

getAutoConfigurationEntry(annotationMetadata);  //给容器中批量导入一下组件

那么就要了解一下 **getAutoConfigurationEntry(annotationMetadata); ** 进入这个方法:

 protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
     									//获取所有候选的配置
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
     //configurations={result={Collections$UnmodifiableRandomAccessList@4786} size =142
    //也就是说这里有142 个候选的配置
 }
		configurations = removeDuplicates(configurations);
		//configurations{{result={ArrayList@4797 size=142}}} //移除了重复的还剩142 


		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		//这一句的意思是从给定的元注解annotationMetadata和attributes 中获取一组排除项
	//annotationMetadata:这是注解的元数据对象,其中包括有关注解的信息,例如名称,属性等
//attributes:这是一个属性对象,包含上下文相关的属性信息
   //excusions 中放的就是 根据annotationMetadata 和attributes  确定的排除的配置项
	//excusions 结果 =>  result=linkedHashSet@4942 size = 0  排除0项

		checkExcludedClasses(configurations, exclusions); //这句的意思是 检查configurations和exclusions  排除项目和 候选配置项有没有冲突  

		configurations.removeAll(exclusions);//在候选配置类中 移除 确定的排除项 


		configurations = getConfigurationClassFilter().filter(configurations);//过滤后的候选配置类
		//configurations=>  ArrayList@5085 size=24
		fireAutoConfigurationImportEvents(configurations, exclusions);//出发自动导入事件,根据候选配置 和 剔除配置 来通知SPRING BOOT 哪些需要配置需要导入
		return new AutoConfigurationEntry(configurations, exclusions);
		//configurations size=24    size=0
	} 

上面我们已经了解到 这条语句获取了所有的候选配置, 那么他是怎么获取的呢

List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

通过断点调试 发现这条语句与下面的方法有关系

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())
			.getCandidates();
    //configurations size=142
		Assert.notEmpty(configurations,
				"No auto configuration classes found in "
						+ "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
						+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

关键语句

List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).getCandidates();

其中getCandidates(); 的作用是返回一个列表 这列表有142条数据, 根据是

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中读取的自动配置类的全限定名

文件里面写死了 SpringBoot 一启动就要给容器中加载的所有配置类

虽然SpringBoot 启动的时候默认全部加载, 但是最终会按需配置: 由于 SpringBoot 条件装配的存在 @ConditionalOn 导致了一些类并不会被加载, 也就实现了按需加载的功能!

修改默认配置

SpringBoot 在底层会配好所有的组件, 如果用户配置了相同的组件 则SpringBoot底层的组件就会失效,原因是SpringBoot 在底层存在大量的ConditionalOnMissingBean

总结:

  • SpringBoot 先加载所有的自动配置类

  • 每个配置类按照条件生效,默认会绑定配置文件的值

  • 生效的配置类就会给容器中装配很多的组件

  • 只要容器中有这些组件,相当于这些功能就有了

  • 只要用户有自己配置的,就以用户的优先

  • 定制化配置

    • 用户直接自己@BEAN替换底层的组件
    • 用户去配置文件里面修改这些值

    xxxxAutoConfiguration -> 组件 ->xxxxproperties.class -> application.properties