SpringBoot(五)@EnableAutoConfiguration注解的工作原理

目录

Demo

源码解析

1、isEnabled()方法:

2、loadMetadata()方法:

3、getAutoConfigurationEntry()方法

3.1、getAttributes()方法

3.2、getCandidateConfigurations()方法

3.3、getExclusions()方法

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

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章