目录
3、getAutoConfigurationEntry()方法
3.2、getCandidateConfigurations()方法
3.4、返回值AutoConfigurationEntry对象
SpringBoot版本:2.1.1
复习一下,在之前关于Spring Boot的第三篇博文中,使用EnvironmentPostProcessor接口可以加载外部配置文件,要在META-INF/spring.factories文件中注册,key是EnvironmentPostProcessor接口全类名,value是实现类的全类名。
@EnableAutoConfiguration也可以加载第三方配置,同样要在META-INF/spring.factories文件中注册,key是@EnableAutoConfiguration的全路径,value是需要加载的类或配置类的全路径,可以是普通类或者配置类,如果是配置类,里面的bean会被纳入spring容器,多个用逗号隔开。
Demo
先来看一个@EnableAutoConfiguration导入第三方配置的例子,随便建一个项目SpringBoot_OutsideConfig,然后建一个bean还有一个配置类。项目结构如下:
pom.xml如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.eastcom</groupId>
<artifactId>SpringBoot_OutsideConfig</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
</dependencies>
</project>
OutSideBean:
public class OutSideBean {
public OutSideBean() {
System.out.println("===============>>>OutSideBean被创建");
}
}
OutsideConfiguration:
public class OutsideConfiguration {
@Bean
public Runnable outsideRunnable() {
return ()->{};
}
}
建好以后在项目上右键-->Run As-->Maven Install。这个项目就先放一边。然后在前面的父工程下新建子项目SpringBoot_EnableAutoConfiguration。
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.eastcom</groupId>
<artifactId>SpringBoot_Demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>SpringBoot_EnableAutoConfiguration</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 导入刚刚创建的项目 -->
<dependency>
<groupId>com.eastcom</groupId>
<artifactId>SpringBoot_OutsideConfig</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
然后在resources下新建META-INF/spring.factories文件,文件中内容如下,key是@EnableAutoConfiguration注解的全路径,value是你要加载的类的全路径,多个用逗号分隔。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.eastcom.outside.bean.OutSideBean,com.eastcom.outside.config.OutsideConfiguration
然后新建主类。
@ComponentScan
@EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
System.out.println(context.getBean(OutSideBean.class));
System.out.println(context.getBean(OutsideConfiguration.class));
System.out.println(context.getBeansOfType(Runnable.class));
}
}
运行。
可以看到OutSideBean和配置类里面的runnable以及配置类本身都已经加载进来了。
源码解析
这里如果把整个类的源码放进来肯定是不怎么方便看的,我个人是不喜欢这样把所有源码一股脑全粘贴进来,所以我把单个方法抽出来具体讲。这样也方便浏览。
从Application类点到@EnableAutoConfiguration注解里面看看源码。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
//这是一个配置项,后面会看到是起什么作用
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
//根据Class<?>排除,就是自动装配的时候排除你指定类,下面的属性也是一样
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
//根据类全路径排除
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
看到一个@Import注解,参数是一个类,再点AutoConfigurationImportSelector进去看源码。
@Import注解用来导入一个或多个类(表示被spring容器托管),如果是一个配置类(配置类里的bean会被spring容器托管)
点进去看DeferredImportSelector。
点进去看ImportSelector。
ImportSelector接口,只有一个方法,返回值是一个String数组,方法上面的介绍是选择并返回应导入的类的名称,基于注解的原数据导入配置类。所以方法返回的是一个Class全路径的String数组,返回的Class会被Spring容器管理。
现在回去看AutoConfigurationImportSelector的selectImports方法。里面的两个主要方法我用框出来了。
1、isEnabled()方法:
返回的是一个boolean值。其中EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY在@EnableAutoConfiguration注解中定义,String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";,在博客开头也讲了,获取这个配置项的值,默认是true。也就是说默认是开启自动配置。
2、loadMetadata()方法:
可以看到最后是将META-INF/spring-autoconfigure-metadata.properties文件的元数据放到了PropertiesAutoConfigurationMetadata对象的properties属性,该类实现了AutoConfigurationMetadata接口。
spring-autoconfigure-metadata.properties文件在spring-boot-autoconfigure-2.1.1.RELEASE.jar/META-INF下。
3、getAutoConfigurationEntry()方法
同样我把主要要看的几个地方框出来了。
3.1、getAttributes()方法
返回AnnotationAttributes实例,该包含了@EnableAutoConfiguration注解的属性值。
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
//得到@EnableAutoConfiguration注解的类型,也就是org.springframework.boot.autoconfigure.EnableAutoConfiguration
String name = getAnnotationClass().getName();
//getAnnotationAttributes(String annotationName,Boolean classValuesAsString):检索给定类型的注释的属性。
//annotationName:要查找的注释类型的完全限定类名
//classValuesAsString:是否将类引用转换为String类名,以便在返回的Map中将值作为值转换,而不是可能必须首先加载的类引用。
//fromMap(Map<String,Object> map):根据给定的Map返回AnnotationAttributes实例。
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(name, true));
Assert.notNull(attributes,
() -> "No auto-configuration attributes found. Is "
+ metadata.getClassName() + " annotated with "
+ ClassUtils.getShortName(name) + "?");
return attributes;
}
/**
* 返回 @EnableAutoConfiguration注解的类型
* @return the annotation class
*/
protected Class<?> getAnnotationClass() {
return EnableAutoConfiguration.class;
}
3.2、getCandidateConfigurations()方法
在讲这个方法前说一下SpringFactoriesLoader.loadFactoryNames()这个方法
该方法的作用是使用给定的类加载器从“META-INF / spring.factories”加载给定类型的工厂实现的完全限定类名。也就是前面讲的在spring.factories文件中注册的,使用@EnableAutoConfiguration的全路径做key,需要加载的类或配置做value。返回一个List<String>。
现在再来看这个方法你就一下能看懂了吧。
3.3、getExclusions()方法
/*
* 获取注解属性
*/
protected Set<String> getExclusions(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
Set<String> excluded = new LinkedHashSet<>();
excluded.addAll(asList(attributes, "exclude"));
excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
excluded.addAll(getExcludeAutoConfigurationsProperty());
return excluded;
}
private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
// 获取配置项spring.autoconfigure.exclude的值
private List<String> getExcludeAutoConfigurationsProperty() {
if (getEnvironment() instanceof ConfigurableEnvironment) {
Binder binder = Binder.get(getEnvironment());
return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class)
.map(Arrays::asList).orElse(Collections.emptyList());
}
String[] excludes = getEnvironment()
.getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
}
// attributes.getStringArray():获取存储在指定attributeName的字符串数组下的值
protected final List<String> asList(AnnotationAttributes attributes, String name) {
String[] value = attributes.getStringArray(name);
return Arrays.asList((value != null) ? value : new String[0]);
}
3.4、返回值AutoConfigurationEntry对象
最后是将需要装配和排除的类封装成了AutoConfigurationEntry对象返回。
流程总结
手滑截图文字还没写完不知道点哪了,就完成了,就这样吧,我在下面接着写。(无奈╮(╯--╰)╭)
6、把需要排除的类移除
7、进行过滤,AutoConfigurationImportFilter的实现类包括OnBeanCondition、OnClassCondition,前面有讲Condition,这里主要就是过滤一些条件不满足的类。
8、就是广播事件,得到AutoConfigurationImportListener所有实现类,然后生成事件进行广播。
9、最后把需要装配和排除的类完全限定名封装成了AutoConfigurationEntry对象返回
项目源码:https://github.com/AiHePing/SpringBoot_Demo