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

37次阅读

共计 8285 个字符,预计需要花费 21 分钟才能阅读完成。

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

正文完
 0