乐趣区

关于java:深度剖析Spring-Boot自动装配机制实现原理

在后面的剖析中,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";
    }
}
@Configuration
public 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 导入

@SpringBootApplication
public 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 带你学架构
如果本篇文章对您有帮忙,还请帮忙点个关注和赞,您的保持是我一直创作的能源。欢送关注同名微信公众号获取更多技术干货!

退出移动版