@EnableAutoConfiguration 和 @Import 的原理

(version:springboot 2.3.2)

@EnableAutoConfiguration 的作用


 

@EnableAutoConfiguration 的作用是开启 Spring 应用上下文的自动配置,它会尝试去猜测和配置我们所需要的 bean。 例如:如果我们的 classpath 中有 tomcat-embedded.jar,那么我们可能需要一个 TomcatServletWebServerFactory 的 bean。

@EnableAutoConfiguration 试图尽可能的智能化: 1, 当我们的 classpath 中存在某些 jar 或者类时,它会帮助我们自动配置 bean; 2, 当我们定义自己的配置时,自动配置的 bean 将不再加载。(具体是通过 一系列的 @ConditionalOnXxx 来实现的)

我们也可以通过注解中的 exclude() 来手动排除任何不想用的配置。 如果没有权限访问到指定排除的类的话,可以使用 excludeName(),或者 spring.autoconfigure.exclude 来指定

当使用 @SpringBootApplication 时,@EnableAutoConfiguration 是自动启用的。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    ...
}

 

org.springframework.boot.autoconfigure.SpringBootApplication
org.springframework.boot.autoconfigure.EnableAutoConfiguration 
这两个注解都来自于 spring-boot-autoconfigure-xx.RELEASE.jar

 

@EnableAutoConfiguration 的原理


 

@Enable 开头的注解上一般都有 @Import 来指定注解的逻辑实现类。

@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 {};
}

@EnableAutoConfiguration 注解继承了 @Import,@Import 导入的 AutoConfigurationImportSelector 实现了 ImportSelector 接口。

所以,@EnableAutoConfiguration 的原理与 @Import 的用法密不可分。 其实,@EnableXxx 的奥秘就是 @Import 的使用和原理。

AutoConfigurationImportSelector#selectImports() 调用栈:

1. AutoConfigurationImportSelector#getCandidateConfigurations
    1.1 AutoConfigurationImportSelector#getSpringFactoriesLoaderFactoryClass() // 返回 org.springframework.boot.autoconfigure.EnableAutoConfiguration
    1.2 SpringFactoriesLoader#loadFactoryNames // 通过 SpringBoot SPI 去加载 spring.factories 中 key=org.springframework.boot.autoconfigure.EnableAutoConfiguration 的值

通过 AutoConfigurationImportSelector#selectImports() 返回的自动配置类,ConfigurationClassParser#processImports() 方法再去加载和处理这些配置类。这就是 @EnableAutoConfiguration 的核心原理。

 

@Import 的使用和原理


 

ImportSelector

ImportSelector 接口可以根据给定的条件来决定导入哪些 @Configuration 类。 ImportSelector 可以实现以下任何 Aware 接口,这些 Aware 接口会在 selectImports() 之前被调用:

EnvironmentAware
BeanFactoryAware
BeanClassLoaderAware
ResourceLoaderAware

或者,也可以使用构造函数来注入这些类:

Environment
BeanFactory
ClassLoader
ResourceLoader

 

/**
* 根据 @Configuration 类给定的 AnnotationMetadata 注解元数据信息(具体是指 @EnableXxx 的元数据信息)选择并返回应该导入的类的名称。
* @return the class names, or an empty array if none
*/
String[] selectImports(AnnotationMetadata importingClassMetadata)

方法的解释很明确,就是根据 selectImports() 方法入参里传入的 AnnotationMetadata(具体是指 @EnableXxx 注解的元数据信息),selectImports() 会返回需要导入的 @Configuration 类的名称。

ImportSelector 的处理最终由 ConfigurationClassParser 来处理的,详细参看 org.springframework.context.annotation.ConfigurationClassParser#processImports()

 

ImportSelector 的实现类通常会与 @Import 以相同的方式进行处理,但是,我们也可以将导入的选择推迟到所有的 @Configuration 类都处理完毕(详细信息请参阅 DeferredImportSelector)。

 

DeferredImportSelector


 

DeferredImportSelector 是 ImportSelector 的变种,在处理完所有 @Configuration bean 之后运行。当所选导入是 @Conditional 时,这种选择器特别有用。DeferredImportSelector 的实现类还可以提供一个导入组 getImportGroup(),该组可以跨不同的选择器提供额外的排序和过滤逻辑。

我想 DeferredImportSelector 延迟处理的原因可能是: 让所有的 @Configuration 执行完之后,该加载到容器中的 bean 都已经加载了,这时再去加载 DeferredImportSelector 所要选择导入的 @Configuration 时, @ConitionalOnBean 就可以发挥它的作用了,否则,如果提前加载了 DeferredImportSelector 所要选择导入的 @Configuration,这时,用户自定义的 @Configuration 还没有加载呢,结果框架自动导入的配置就优先加载进去了,所以 @ConitionalOnBean 就起不到想要的效果了。

查看源码可以发现 ConfigurationClassParser#processImports 对 DeferredImportSelector 有特殊的处理,也就是这个延迟的处理。

 

ImportBeanDefinitionRegistrar


 

通过 ImportBeanDefinitionRegistrar 可以在处理 @Configuration 类时注册其他 beanDefinition 到容器中。 ImportBeanDefinitionRegistrar 可以实现以下任何 Aware 接口,这些 Aware 接口会在 registerBeanDefinitions() 之前被调用:

EnvironmentAware
BeanFactoryAware
BeanClassLoaderAware
ResourceLoaderAware

或者,也可以使用构造函数来注入这些类:

Environment
BeanFactory
ClassLoader
ResourceLoader

 

/**
* 根据 @Configuration 类给定的 AnnotationMetadata 注解元数据信息(具体是指 @EnableXxx 的元数据信息),注册所需的 beanDefinition。
*/
void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)

具体是通过 BeanDefinitionRegistry 来注册 beanDefinition 的。

用法举例:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
   boolean proxyTargetClass() default false;
   boolean exposeProxy() default false;
}

通过 @Import 来导入一个 ImportBeanDefinitionRegistrar 的实例,ImportBeanDefinitionRegistrar#registerBeanDefinitions() 被调用时可以往容器中注入 beanDefinition。

 

总结


 

  1. @EnableXxx 注解是用在 @Configuration 标记的类上,用来自动配置和加载一些 @Configuration 类或者 bean 的。 所以,@EnableXxx 一定是与 @Configuration 一起使用的。

  2. @EnableXxx 一定会与 @Import 一起使用,通过 @Import 来达到自动配置的目的。

@Import 有 3 种用法,分别是:

  1. 导入具体的配置类

  2. 导入 ImportSelector(或者 DeferredImportSelector)

  3. 导入 ImportBeanDefinitionRegistrar

这 3 种类的处理最终都是由org.springframework.context.annotation.ConfigurationClassParser#processImports() 方法来处理导入的。

实例:

导入具体的配置类 :直接导入配置类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}

 

导入ImportSelector:通过 ImportSelector#selectImports() 来选择需要导入的配置类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
    ...
}

 

导入DeferredImportSelector:主要是使用了 DeferredImportSelector 的延迟导入能力

@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 {};
}

 

导入ImportBeanDefinitionRegistrar:通过 ImportBeanDefinitionRegistrar 来向容器中注入 bean

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
   boolean proxyTargetClass() default false;
   boolean exposeProxy() default false;
}

 

参考:
https://my.oschina.net/floor/blog/4354771

 

posted on 2020-08-29 19:48  快鸟  阅读(3899)  评论(0编辑  收藏  举报