在后面的剖析中,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
- 第一种就是后面演示过的,基于一般 bean 或者带有 @Configuration 的 bean 进行诸如
- 实现 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 带你学架构
!
如果本篇文章对您有帮忙,还请帮忙点个关注和赞,您的保持是我一直创作的能源。欢送关注同名微信公众号获取更多技术干货!