在后面的剖析中,Spring Framework始终在致力于解决一个问题,就是如何让bean的治理变得更简略,如何让开发者尽可能的少关注一些根底化的bean的配置,从而实现主动拆卸。所以,所谓的主动拆卸,实际上就是如何主动将bean装载到Ioc容器中来。

实际上在spring 3.x版本中,Enable模块驱动注解的呈现,曾经有了肯定的主动拆卸的雏形,而真正可能实现这一机制,还是在spirng 4.x版本中,conditional条件注解的呈现。ok,咱们来看一下spring boot的主动拆卸是怎么一回事。

主动拆卸的演示

 <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-data-redis</artifactId></dependency> 
spring:    redis:      host: 127.0.0.1       port: 6379
 @Autowired    private RedisTemplate<String,String>redisTemplate;

依照上面的程序增加starter,而后增加配置,应用RedisTemplate就能够应用了? 那大家想没想过一个问题,为什么RedisTemplate能够被间接注入?它是什么时候退出到Ioc容器的呢? 这就是主动拆卸。主动拆卸能够使得classpath下依赖的包相干的bean,被主动装载到Spring Ioc容器中,怎么做到的呢?

深入分析EnableAutoConfiguration

EnableAutoConfiguration的次要作用其实就是帮忙springboot利用把所有符合条件的@Configuration配置都加载到以后SpringBoot创立并应用的IoC容器中。

再回到EnableAutoConfiguration这个注解中,咱们发现它的import是这样

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

然而从EnableAutoCOnfiguration下面的import注解来看,这外面并不是引入另外一个Configuration。而是一个ImportSelector。这个是什么货色呢?

AutoConfigurationImportSelector是什么?

Enable注解不仅仅能够像后面演示的案例一样很简略的实现多个Configuration的整合,还能够实现一些简单的场景,比方能够依据上下文来激活不同类型的bean,@Import注解能够配置三种不同的class

  1. 第一种就是后面演示过的,基于一般bean或者带有@Configuration的bean进行诸如
  2. 实现ImportSelector接口进行动静注入

实现ImportBeanDefinitionRegistrar接口进行动静注入

CacheService

public class CacheService {
}

LoggerService

public class LoggerService {
}

EnableDefineService

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented @Inherited  --容许被继承@Import({GpDefineImportSelector.class})public @interface EnableDefineService {    String[] packages() default "";}

GpDefineImportSelector

public class GpDefineImportSelector implements ImportSelector {    @Override    public String[] selectImports(AnnotationMetadata annotationMetadata) {        //取得指定注解的详细信息。咱们能够依据注解中配置的属性来返回不同的class,        //从而能够达到动静开启不同性能的目标    annotationMetadata.getAllAnnotationAttributes(EnableDefineService.class.getName(),true)            .forEach((k,v) -> {                log.info(annotationMetadata.getClassName());                log.info("k:{},v:{}",k,String.valueOf(v));            });        return new String[]{CacheService.class.getName()};    }}

EnableDemoTest

@SpringBootApplication@EnableDefineService(name = "gupao",value = "gupao")public class EnableDemoTest {    public static void main(String[] args) {        ConfigurableApplicationContext ca=SpringApplication.run(EnableDemoTest.class,args);        System.out.println(ca.getBean(CacheService.class));        System.out.println(ca.getBean(LoggerService.class));    }}

理解了selector的基本原理之后,后续再去剖析AutoConfigurationImportSelector的原理就很简略了,它实质上也是对于bean的动静加载。

@EnableAutoConfiguration注解的实现原理

理解了ImportSelector和ImportBeanDefinitionRegistrar后,对于EnableAutoConfiguration的了解就容易一些了

它会通过import导入第三方提供的bean的配置类:AutoConfigurationImportSelector

@Import(AutoConfigurationImportSelector.class)

从名字来看,能够猜到它是基于ImportSelector来实现基于动静bean的加载性能。之前咱们讲过Springboot @Enable*注解的工作原理ImportSelector接口selectImports返回的数组(类的全类名)都会被纳入到spring容器中。

那么能够猜想到这里的实现原理也肯定是一样的,定位到AutoConfigurationImportSelector这个类中的selectImports办法

selectImports

public String[] selectImports(AnnotationMetadata annotationMetadata) {   if (!isEnabled(annotationMetadata)) {      return NO_IMPORTS;   }// 从配置文件(spring-autoconfigure-metadata.properties)中加载 AutoConfigurationMetadata   AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader         .loadMetadata(this.beanClassLoader);// 获取所有候选配置类EnableAutoConfiguration   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(         autoConfigurationMetadata, annotationMetadata);   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}

getAutoConfigurationEntry

protected AutoConfigurationEntry getAutoConfigurationEntry(      AutoConfigurationMetadata autoConfigurationMetadata,      AnnotationMetadata annotationMetadata) {   if (!isEnabled(annotationMetadata)) {      return EMPTY_ENTRY;   }//获取元注解中的属性   AnnotationAttributes attributes = getAttributes(annotationMetadata);//应用SpringFactoriesLoader 加载classpath门路下META-INF\spring.factories中,//key= org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的value   List<String> configurations = getCandidateConfigurations(annotationMetadata,         attributes);//去重   configurations = removeDuplicates(configurations);//利用exclusion属性   Set<String> exclusions = getExclusions(annotationMetadata, attributes);   checkExcludedClasses(configurations, exclusions);   configurations.removeAll(exclusions);//过滤,查看候选配置类上的注解@ConditionalOnClass,如果要求的类不存在,则这个候选类会被过滤不被加载   configurations = filter(configurations, autoConfigurationMetadata);   //播送事件fireAutoConfigurationImportEvents(configurations, exclusions);   return new AutoConfigurationEntry(configurations, exclusions);}

实质上来说,其实EnableAutoConfiguration会帮忙springboot利用把所有合乎@Configuration配置都加载到以后SpringBoot创立的IoC容器,而这外面借助了Spring框架提供的一个工具类SpringFactoriesLoader的反对。以及用到了Spring提供的条件注解@Conditional,选择性的针对须要加载的bean进行条件过滤

SpringFactoriesLoader

为了给大家补一下根底,我在这里简略剖析一下SpringFactoriesLoader这个工具类的应用。它其实和java中的SPI机制的原理是一样的,不过它比SPI更好的点在于不会一次性加载所有的类,而是依据key进行加载。

首先,SpringFactoriesLoader的作用是从classpath/META-INF/spring.factories文件中,依据key来加载对应的类到spring IoC容器中。接下来带大家实际一下

创立内部我的项目jar

<dependency>  <groupId>org.springframework</groupId>  <artifactId>spring-context</artifactId>  <version>4.3.13.RELEASE</version></dependency>

创立bean以及config

public class GuPaoCore {    public String study(){        System.out.println("good good study, day day up");        return "GuPaoEdu.com";    }}@Configurationpublic class GuPaoConfig {    @Bean    public GuPaoCore guPaoCore(){        return new GuPaoCore();    }}

创立另外一个工程(spring-boot)

把后面的工程打包成jar,以后我的项目依赖该jar包

<dependency>    <groupId>com.gupaoedu.practice</groupId>    <artifactId>Gupao-Core</artifactId>    <version>1.0-SNAPSHOT</version></dependency>

通过上面代码获取依赖包中的属性

运行后果会报错,起因是GuPaoCore并没有被Spring的IoC容器所加载,也就是没有被EnableAutoConfiguration导入

@SpringBootApplicationpublic class SpringBootStudyApplication {    public static void main(String[] args) throws IOException {        ConfigurableApplicationContext ac=SpringApplication.run(SpringBootStudyApplication.class, args);        GuPaoCore gpc=ac.getBean(GuPaoCore.class);        System.out.println(gpc.study());    }}

解决方案

在GuPao-Core我的项目resources下新建文件夹META-INF,在文件夹上面新建spring.factories文件,文件中配置,key为自定配置类EnableAutoConfiguration的全门路,value是配置类的全门路

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gupaoedu.practice.GuPaoConfig

从新打包,从新运行SpringBootStudyApplication这个类。

能够发现,咱们编写的那个类,就被加载进来了。

@EnableAutoConfiguration注解的实现原理

理解了ImportSelector和ImportBeanDefinitionRegistrar后,对于EnableAutoConfiguration的了解就容易一些了

它会通过import导入第三方提供的bean的配置类:AutoConfigurationImportSelector

@Import(AutoConfigurationImportSelector.class)

从名字来看,能够猜到它是基于ImportSelector来实现基于动静bean的加载性能。之前咱们讲过Springboot @Enable*注解的工作原理ImportSelector接口selectImports返回的数组(类的全类名)都会被纳入到spring容器中。

那么能够猜想到这里的实现原理也肯定是一样的,定位到AutoConfigurationImportSelector这个类中的selectImports办法

selectImports

public String[] selectImports(AnnotationMetadata annotationMetadata) {   if (!isEnabled(annotationMetadata)) {      return NO_IMPORTS;   }// 从配置文件(spring-autoconfigure-metadata.properties)中加载 AutoConfigurationMetadata    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader         .loadMetadata(this.beanClassLoader);// 获取所有候选配置类EnableAutoConfiguration   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(         autoConfigurationMetadata, annotationMetadata);   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}

getAutoConfigurationEntry

protected AutoConfigurationEntry getAutoConfigurationEntry(      AutoConfigurationMetadata autoConfigurationMetadata,      AnnotationMetadata annotationMetadata) {   if (!isEnabled(annotationMetadata)) {      return EMPTY_ENTRY;   }//获取元注解中的属性   AnnotationAttributes attributes = getAttributes(annotationMetadata);//应用SpringFactoriesLoader 加载classpath门路下META-INF\spring.factories中,//key= org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的value   List<String> configurations = getCandidateConfigurations(annotationMetadata,         attributes);//去重   configurations = removeDuplicates(configurations);//利用exclusion属性   Set<String> exclusions = getExclusions(annotationMetadata, attributes);   checkExcludedClasses(configurations, exclusions);   configurations.removeAll(exclusions);//过滤,查看候选配置类上的注解@ConditionalOnClass,如果要求的类不存在,则这个候选类会被过滤不被加载   configurations = filter(configurations, autoConfigurationMetadata);   //播送事件fireAutoConfigurationImportEvents(configurations, exclusions);   return new AutoConfigurationEntry(configurations, exclusions);}

实质上来说,其实EnableAutoConfiguration会帮忙springboot利用把所有合乎@Configuration配置都加载到以后SpringBoot创立的IoC容器,而这外面借助了Spring框架提供的一个工具类SpringFactoriesLoader的反对。以及用到了Spring提供的条件注解@Conditional,选择性的针对须要加载的bean进行条件过滤

版权申明:本博客所有文章除特地申明外,均采纳 CC BY-NC-SA 4.0 许可协定。转载请注明来自 Mic带你学架构
如果本篇文章对您有帮忙,还请帮忙点个关注和赞,您的保持是我一直创作的能源。欢送关注同名微信公众号获取更多技术干货!