如何理解自动装配
自动装配就是spring boot根据我们引用的jar包、配置文件配置项值、环境变量值、操作系统等等自定义可变量,自动将对应的组件(对象)注入到我们的应用中;
比如我们在pom里依赖了spring-boot-starter-web,那么Springboot就会将嵌入式Tomcat作为web容器并启动一个容器;或者我在yaml中配置了一个redis的地址就可以直接在应用里直接用RedisTemplate等等...
自动装配的实现
spring boot的自动装配主要是由于@SpringBootApplication注解中的 @EnableAutoConfiguration注解发挥作用的。重点分析 @Import({AutoConfigurationImportSelector.class})
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
1.@Target
@Target 说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
取值(ElementType)有:
1.CONSTRUCTOR:用于描述构造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部变量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述参数
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
2.@Retention
作用:定义被它所注解的注解保留多久,一共有三种策略,定义在RetentionPolicy枚举中.
从注释上看:
source:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;被编译器忽略
class:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期
runtime:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。
3.@Documented
作用:是否展示注解
4.@Inherited
@Inherited是一个标识,用来修饰注解
作用:如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解
注意:
接口用上个@Inherited修饰的注解,其实现类不会继承这个注解
父类的方法用了@Inherited修饰的注解,子类也不会继承这个注解
当用了@Inherited修饰的注解的@Retention是RetentionPolicy.RUNTIME,则增强了继承性,在反射中可以获取得到
5.@AutoConfigurationPackage
作用:实质上,它负责保存标注相关注解的类的所在包路径。使用一个BasePackage类,保存这个路径。然后使用@Import注解将其注入到ioc容器中。这样,可以在容器中拿到该路径。
6.@Import({AutoConfigurationImportSelector.class})
可以看出这个注解是 @Import 标签的复合注解(可以认为@EnableAutoConfiguration具有@Import 一样的功能,这是由spring内部提供的一套注解解析api支撑),被引入的配置类是:
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector 这个类就是自动装配的入口了;熟悉Spring的应该知道,被Import的配置类org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports方法会在Spring容器生命周期的 invokeBeanFactoryPostProcessors 阶段(bean工厂后置处理器)调用。
自动装配原理的分析
主要针对AutoConfigurationImportSelector类做分析
public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return NO_IMPORTS; } else { //@1:从配置文件 spring-autoconfigure-metadata.properties 获得自动装配过滤规则元数据 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); //@2:通过spring-boot的SPI机制,获得所有自动装配配置主类信息 AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } }
loadMetadata
这行代码主要逻辑是从 spring-autoconfigure-metadata.properties(解释配置如下) 里获得自动装配过滤相关的元数据信息。
这些元数据的获得是通过org.springframework.boot.autoconfigure.AutoConfigurationMetadataLoader#loadMetadata(java.lang.ClassLoader, java.lang.String)获得,这里的path是一个固定路径:
protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";
spring-autoconfigure-metadata.properties
首先说明下,这个 spring-autoconfigure-metadata.properties 文件存储的是”待自动装配候选类“过滤的计算规则,这个信息很重要,框架会根据里面的规则逐一对候选类进行计算看是否需要被自动装配进容器,并不是全部加载;
spring-autoconfigure-metadata.properties 内容格式 (自动配置的类全名.条件Condition=值):
org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration.ConditionalOnClass= org.springframework.amqp.rabbit.annotation.EnableRabbit
getAutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } else { //NO.1:获得 @EnableAutoConfiguration注解标签上所有的属性值 AnnotationAttributes attributes = this.getAttributes(annotationMetadata); //NO.2:从spring.factories文件里获得 EnableAutoConfiguration key对应的所有自动装配引导类,并去掉一些重复的(因为有可能用户自定义引入了一些重复的类) List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); configurations = this.removeDuplicates(configurations); //排除需要排除的类,具体操作是通过@SpringBootApplication 注解中的 exclude、excludeName、环境属性中的 spring.autoconfigure.exclude配置 Set<String> exclusions = this.getExclusions(annotationMetadata, attributes); this.checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); //NO.3:根据 spring-autoconfigure-metadata.properties 中配置的规则过虑掉一部分引导类 configurations = this.filter(configurations, autoConfigurationMetadata); this.fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); } }
spring.factories 这个文件存储了spring-boot所有默认支持的待自动装配候选类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\ org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
这里每一个配置项都是被 @Configuration 标注的类,一般也会带上 @Import注解、@AutoConfigureAfter或者 @Conditional 衍生注解(反正能够引发一些自定义的逻辑执行,这是spring boot开发套路),最终向容器返回候选者集合并由容器执行后续逻辑。
filter 过滤候选类逻辑
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) { long startTime = System.nanoTime(); //@A String[] candidates = StringUtils.toStringArray(configurations); boolean[] skip = new boolean[candidates.length]; boolean skipped = false; // 表示当前的候选类是否要被跳过即不被自动装配 //@B for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) { invokeAwareMethods(filter); //@C boolean[] match = filter.match(candidates, autoConfigurationMetadata); for (int i = 0; i < match.length; i++) { //@D if (!match[i]) { skip[i] = true; candidates[i] = null; skipped = true; //当前候选类需要被跳过,即不被自动装配 } } } //@E if (!skipped) { // 没有一个需要跳过,即全部候选类都会被装配 return configurations; } List<String> result = new ArrayList<>(candidates.length); for (int i = 0; i < candidates.length; i++) { //@F if (!skip[i]) { result.add(candidates[i]); } } if (logger.isTraceEnabled()) { int numberFiltered = configurations.size() - result.size(); logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms"); } return new ArrayList<>(result); }
@A:将所有的待处理类(所有jar包里META-INFO/spring.factories里key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置)从List转成数组
@B:getAutoConfigurationImportFilters()从spring.factories里获得key为 org.springframework.boot.autoconfigure.AutoConfigurationImportFilter 的配置类,获得的类都是实现了AutoConfigurationImportFilter接口;
有:OnWebApplicationCondition,OnBeanCondition,OnClassCondition三个类
@C:分别调用3个类的match方法返回boolean[] 数组;
@D:数组中元素为true表示该org.springframework.boot.autoconfigure.EnableAutoConfiguration类合法,为false则表示不合法,需要标记为跳过。
@E:如果其中有一个filter处理结果为“所有类不应该跳过”则直接返回候选类全集
@F:组装返回值,不需要跳过的候选类才加入返回值中
上面代码直接执行 @E 返回的机会较小,只有表示全部候选类都需要被装配才会走这个return 分支
核心过滤代码是在 @C,即以过滤器为外循环,对所有的候选类计算候选类是否要被跳过
@Conditional的条件装配
配置 | 解释 |
ConditionalOnBean/ConditionalOnMissingBean | 容器中存在指定类或者不存在某个Bean时进行Bean加载 |
ConditionalOnCloudPlatform | 只有运行在指定的云平台上才加载指定的bean |
ConditionOnJava | 只有运行在指定版本的Java才会加载Bean |
ConditionalOnProperty | 系统中指定属性有对应的值才加载Bean |
ConditaionalOnClass/ConditionalOnMissingClass | classpath存在指定类或者不存在指定类时进行Bean加载 |
ConditionalOnResource | 要加载的Bean依赖指定的资源是否存在classpath中 |
注:也可以写在 spring-autoconfigure-metadata.properties 文件中
总结
- 通过@Import({AutoConfigurationImportSelector.class})实现配置类的导入,但这里不是传统意义上的单个配置类的装配(多个)
- AutoConfigurationImportSelector类实现了ImportSelector接口,重写了方法selectImports(),它用于实现选择性批量配置类的装配。(在Spring容器生命周期的 invokeBeanFactoryPostProcessors 阶段(bean工厂后置处理器)调用)
- 通过Spring提供的SpringFactoriesLoader机制,扫描classpath路径下的META-INF/spring.factories,读取需要实现自动装配的配置类。
- 通过条件筛选的方式,把不符合条件的配置类移除,最终完成自动装配