Spring把「手动」的复杂裹成了「自动」的温柔
案例
案例一:@EnableXXX注解使用
在一个 Spring MVC 项目,通过给配置类加上一个 @EnableWebMvc 注解,加上之后 Spring 就会注册 Spring MVC 的一系列组件,包括:HandlerMapping,HandlerAdapter,ViewResolver 等。
案例二:Spring Boot自动配置
在一个 Spring Boot应用中会有 @SpringBootApplication 注解修饰启动类,当引入 spring-boot-starter-web 依赖之后,Spring 也会自动地注册 Spring MVC 的一系列组件。
那 Spring 中是如何实现自动注册的能力的呢?先说结论:
Spring 中提供了 @Import 注解可以引入一个配置类或者是配置类的选择器。
当使用一般的 @EnableXXX 注解时实际上是通过 @Import 注解引入了预先定义好的配置类,它会配置一些指定的 Bean 来实现对应的功能。
当使用 Spring Boot 的自动配置功能时实际上是通过 @Import 注解引入了一个配置类的选择器,它会读取配置文件中配置的所有配置类,然后判断该配置类的条件是否满足,如果满足,则引入,否则,则不引入,从而实现自动配置某些功能。
源码分析
@EnableXXX注解实现原理
先看一下 @EnableWebMvc 注解,该注解上通过 @Import 注解引用了一个 DelegatingWebMvcConfiguration 配置类,它上面有 @Configuration 注解修饰。代码如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
}
在 DelegatingWebMvcConfiguration 这个配置类的父类 WebMvcConfigurationSupport 中定义了很多由 @Bean 注解修饰的方法,这些就是 Spring 会注册的 Spring MVC 组件类。代码如下:
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
// 定义HandlerMapping组件Bean
@Bean
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
// 省略代码
}
// 定义HandlerAdapter组件Bean
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcValidator") Validator validator) {
// 省略代码
}
// 定义ViewResolver组件Bean
@Bean
public ViewResolver mvcViewResolver(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
// 省略代码
}
}
在3 个案例看透 Spring @Component 扫描:从普通应用到 Spring Boot文章中介绍了 Spring 中如何从 @Configuration 注解修饰的配置类的包扫描路径取扫描 Bean 的。主要是在ConfigurationClassParser 的 doProcessConfigurationClass() 方法中实现的,而对 @Import 注解引用的类也是在该方法中实现的。代码如下:
@Nullable
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
// 省略代码
// 这里处理@Import注解
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// 省略代码
// 这里处理@Bean注解修饰的方法
// Process individual @Bean methods
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
if (methodMetadata.isAnnotated("kotlin.jvm.JvmStatic") && !methodMetadata.isStatic()) {
continue;
}
// 添加到配置类中
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// 省略代码
// No superclass -> processing is complete
return null;
}
在处理 @Import 注解引用的且是 @Configuration 注解修饰的类时,把它当作配置类,递归调用解析配置类的方法 processConfigurationClass(),然后又进入到 doProcessConfigurationClass() 中,解析该类上 @Bean 注解修饰的方法添加到配置类中 。代码如下:
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> filter, boolean checkForCircularImports) {
// 省略代码
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
// 省略代码
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
// 省略代码
// 处理@Configuration注解修饰的类,就是去把它当作配置类继续解析它的配置
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), filter);
}
} finally {
this.importStack.pop();
}
}
}
具体把配置类中的 Bean 方法解析为 Bean 定义则是在 ConfigurationClassPostProcessor 的 processConfigBeanDefinitions() 中调用 ConfigurationClassBeanDefinitionReader 的 loadBeanDefinitionsForBeanMethod() 方法实现的。代码如下:
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// 省略代码
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = CollectionUtils.newHashSet(configCandidates.size());
do {
// 省略代码
parser.parse(candidates);
parser.validate();
// 省略代码
// 这里调用loadBeanDefinitionsForBeanMethod()解析并注册Bean定义
this.reader.loadBeanDefinitions(configClasses);
// 省略代码
}
while (!candidates.isEmpty());
// 省略代码
}
然后在 ConfigurationClassBeanDefinitionReader 的 loadBeanDefinitionsForBeanMethod() 方法中从 @Bean 注解中获取 initMethod,destroyMethod 这些信息,然后注册 Bean 定义。代码如下:
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
// 省略代码
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
// 省略代码
}
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
String methodName = metadata.getMethodName();
// 省略代码
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);
String initMethodName = bean.getString("initMethod");
if (StringUtils.hasText(initMethodName)) {
beanDef.setInitMethodName(initMethodName);
}
String destroyMethodName = bean.getString("destroyMethod");
beanDef.setDestroyMethodName(destroyMethodName);
// 注册Bean定义
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}
Spring Boot 自动配置原理
对于一个 Spring Boot 应用上的 @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 {}
@EnableAutoConfiguration 注解和上面的 @EnableWebMvc 注解类似也是通过 @Import 注解引入了一个类 AutoConfigurationImportSelector,但是这个类却没有 @Configuration 注解修饰,而是实现了 ImportSelector 接口。代码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {}
在上面的 ConfigurationClassParser 类的 processImports() 方法中有一个分支就是判断 @Import 注解引入的类是不是 DeferredImportSelector 接口,如果是则会调用 DeferredImportSelectorHandler 的 handle() 方法进行处理。代码如下:
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> filter, boolean checkForCircularImports) {
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
if (selector instanceof DeferredImportSelector deferredImportSelector) {
// 调用deferredImportSelectorHandler的handle()方法
this.deferredImportSelectorHandler.handle(configClass, deferredImportSelector);
}
}
} finally {
this.importStack.pop();
}
}
}
而 DeferredImportSelectorHandler 的 handle 方法只是先把当前类加入到自己的 deferredImportSelectors 属性中。代码如下:
private class DeferredImportSelectorHandler {
@Nullable
private List<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>();
void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
if (this.deferredImportSelectors == null) {
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
handler.register(holder);
handler.processGroupImports();
}
else {
this.deferredImportSelectors.add(holder);
}
}
}
最后在 ConfigurationClassParser 的 parse() 方法最后调用它的 process() 方法。在 DeferredImportSelectorHandler 的 process() 方法中又调用了 DeferredImportSelectorHolder 的 processGroupImport() 方法。代码如下:
public void parse(Set<BeanDefinitionHolder> configCandidates) {
// 省略代码
this.deferredImportSelectorHandler.process();
}
void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
if (this.deferredImportSelectors == null) {
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
handler.register(holder);
handler.processGroupImports();
}
else {
this.deferredImportSelectors.add(holder);
}
}
private class DeferredImportSelectorGroupingHandler {
@SuppressWarnings("NullAway")
void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
Predicate<String> filter = grouping.getCandidateFilter();
// 调用getImports()方法获取到配置类,然后在递归调用processImports()方法
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
try {
processImports(configurationClass, asSourceClass(configurationClass, filter),
Collections.singleton(asSourceClass(entry.getImportClassName(), filter)),
filter, false);
}
// 省略代码
});
}
}
}
然后调用到了 AutoConfigurationGroup 的 process() 方法,在该方法中会调用 AutoConfigurationImportSelector 的 getAutoConfigurationEntry() 方法,这个里这个类就是通过 @EnableAutoConfiguration 引入的类了。代码如下:
private static final class AutoConfigurationGroup
implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
// 省略代码
// 调用AutoConfigurationImportSelector的getAutoConfigurationEntry()方法
AutoConfigurationEntry autoConfigurationEntry = autoConfigurationImportSelector
.getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
}
在 AutoConfigurationImportSelector 的 getAutoConfigurationEntry() 方法调用 ImportCandidates 读取默认值为 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中列举的配置类,然后过滤掉不满足条件的配置类,过滤的方式可以是判断 CLASSPATH 路径下某些类是否存在。代码如下:
AutoConfigurationImportSelector{
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 省略代码
// 获取所有配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 过滤掉不满足条件的配置类
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation,
getBeanClassLoader());
return configurations;
}
}
public final class ImportCandidates implements Iterable<String> {
private static final String LOCATION = "META-INF/spring/%s.imports";
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
ClassLoader classLoaderToUse = decideClassloader(classLoader);
// 这里就是配置类所在文件,默认是org.springframework.boot.autoconfigure.AutoConfiguration.imports
String location = String.format(LOCATION, annotation.getName());
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
List<String> importCandidates = new ArrayList<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
importCandidates.addAll(readCandidateConfigurations(url));
}
return new ImportCandidates(importCandidates);
}
}
org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中内容如下:

这里以 WebMvcAutoConfiguration 配置类为例,它要不被过滤掉的条件是 CLASSPATH 路径下存在 Servlet, DispatcherServlet, WebMvcConfigurer 这些类,即这些类存在则会解析 WebMvcAutoConfiguration 配置类配置的 Bean,从而实现 Spring MVC 组件的 Bean 的注册。代码如下:
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
public class WebMvcAutoConfiguration {}
Spring把「手动」的复杂裹成了「自动」的温柔的更多相关文章
- 【翻译】西川善司的「实验做出的游戏图形」「GUILTY GEAR Xrd -SIGN-」中实现的「纯卡通动画的实时3D图形」的秘密,后篇
http://www.4gamer.net/games/216/G021678/20140714079/ 连载第2回的本回, Arc System Works开发的格斗游戏「GUILTY G ...
- 何解決 LinqToExcel 發生「無法載入檔案或組件」問題何解決 LinqToExcel 發生「無法載入檔案或組件」問題
在自己的主機上透過 Visual Studio 2013 與 IISExpress 開發與測試都還正常,但只要部署到測試機或正式機,就是沒辦法順利執行,卡關許久之後找我協助.我發現錯誤訊息確實很「一般 ...
- 转:Android开源项目推荐之「网络请求哪家强」 Android开源项目推荐之「网络请求哪家强」
转载自https://zhuanlan.zhihu.com/p/21879931 1. 原则 本篇说的网络请求专指 http 请求,在选择一个框架之前,我个人有个习惯,就是我喜欢选择专注的库,其实在软 ...
- [Q&A]VS 2012 MVC4专案与网站的差异?「ASP.NET组态」的Login账号出现在「新旧两组」会员数据库里面?
原文出處 http://www.dotblogs.com.tw/mis2000lab/archive/2013/08/30/mvc4_vs2012_login_member_db.aspx [Q&a ...
- spring cloud 入门,看一个微服务框架的「五脏六腑」
Spring Cloud 是一个基于 Spring Boot 实现的微服务框架,它包含了实现微服务架构所需的各种组件. 注:Spring Boot 简单理解就是简化 Spring 项目的搭建.配置.组 ...
- 《React Native 精解与实战》书籍连载「Node.js 简介与 React Native 开发环境配置」
此文是我的出版书籍<React Native 精解与实战>连载分享,此书由机械工业出版社出版,书中详解了 React Native 框架底层原理.React Native 组件布局.组件与 ...
- 【翻译】西川善司「实验做出的游戏图形」「GUILTY GEAR Xrd -SIGN-」中实现的「纯卡通动画的实时3D图形」的秘密,前篇(2)
Lighting和Shading(2)镜面反射的控制和模拟次级表面散射技术 http://www.4gamer.net/games/216/G021678/20140703095/index_2.ht ...
- 「懒惰的美德」我用 python 写了个自动生成给文档生成索引的脚本
我用 python 写了一个自动生成索引的脚本 简介:为了刷算法题,建了一个 GitHub仓库:PiperLiu / ACMOI_Journey,记录自己的刷题轨迹,并总结一下方法.心得.想到一个需求 ...
- PHP 文件夹操作「复制、删除、查看大小、重命名」递归实现
PHP虽然提供了 filesize.copy.unlink 等文件操作的函数,但是没有提供 dirsize.copydir.rmdirs 等文件夹操作的函数(rmdir也只能删除空目录).所以只能手动 ...
- SPOJ 16549 - QTREE6 - Query on a tree VI 「一种维护树上颜色连通块的操作」
题意 有操作 $0$ $u$:询问有多少个节点 $v$ 满足路径 $u$ 到 $v$ 上所有节点(包括)都拥有相同的颜色$1$ $u$:翻转 $u$ 的颜色 题解 直接用一个 $LCT$ 去暴力删边连 ...
随机推荐
- Scrum进入疲惫期?三点帮你走出困境
<敏捷软件开发>中提到: "Scrum 可以帮助团队更好地应对变化和不确定性,以及更快地响应客户需求.通过持续的反馈和改进,Scrum 可以提高团队的适应性和灵活性." ...
- 鸿蒙运动项目开发:封装超级好用的 RCP 网络库(下)—— 实战应用
鸿蒙核心技术##运动开发## Remote Communication Kit(远场通信服务) 在之前的文章中,我们详细介绍了如何封装一个功能完备的 RCP 网络库,并探讨了其核心功能和高级特性.在本 ...
- [CF1672G]Cross Xor
G - Cross Xor 对于\((n\&1)\&\&(m\&1)\)的情况,所有行.列的异或和的必须相等(异或和指当前行/列中所有元素的异或和) 每次修改的点\(( ...
- Web前端入门第 75 问:JavaScript 性能优化之事件委托(事件代理)原理
如今 Vue 大行其道,大部分开发场景直接使用 @click 就实现了事件绑定,可能都没思索过 JS 中的事件性能优化. 事件委托原理 事件委托 其主要是利用了事件冒泡这个特性. 以点击事件 clic ...
- oracleINS-13001 环境不满足最低要求
使用windows10等系统安装oracle 11g等版本的数据库时,经常会发现开始安装时弹出[INS-13001 环境不满足最低要求]的提示,此时可以点击[是]继续安装. 也可以点击[否]结束安装, ...
- 腾讯云的devops自动化部署代替jenkins
起因 jenkins太耗内存了,经常导致服务器崩. 了解到devOps也是做类似的服务的,遂用之. serverless framework也可以做这个,但是截至目前,只能够打包node项目. dev ...
- 前端开发系列130-进阶篇之TS、Class and ES5
本文讨论Typescript中的Class同ES5构造函数的对应关系,涉及TypeScript的诸多语法.构造函数.面向对象以及原型对象等相关知识点细节,本文只简单对比并不进行深入展开. TypeSc ...
- iga 入门之 总体合成
简介 摘自 流体力学数值方法 概括地说,总体合成就是将所有单元的\(A_{ij}^{(e)}.f_i^{(e)}\)进行累加,最终形成\(A_{nm}.f_n\),从而产生总体有限元方程 \[A_{n ...
- POLIR-Laws-《消费者权益保护法》:商家制假售假 与 拼多多+中国人寿财险 的欺诈行为不正当竞争得利 赔偿: 全国人大: 建议完善惩罚性赔偿制度
<消费者权益保护法>规定: 经营者如果存在欺诈行为,需要按照消费者的要求增加赔偿其受到的损失, 增加赔偿的金额为消费者购买商品的价款或者接受服务的费用的三倍: 增加赔偿的金额不足五百元的, ...
- SciTech-BigDataAI-ImageProcessing-OpenCV-OpenCV modules
OpenCV modules https://docs.opencv.org/3.4/ Introduction OpenCV Tutorials OpenCV-Python Tutorials Op ...