当前位置: 首页 > news >正文

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@Beanpublic RequestMappingHandlerAdapter requestMappingHandlerAdapter(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,@Qualifier("mvcConversionService") FormattingConversionService conversionService,@Qualifier("mvcValidator") Validator validator) {// 省略代码}// 定义ViewResolver组件Bean@Beanpublic 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 annotationsprocessImports(configClass, sourceClass, getImports(sourceClass), filter, true);// 省略代码// 这里处理@Bean注解修饰的方法// Process individual @Bean methodsSet<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 completereturn 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 定义则是在 ConfigurationClassPostProcessorprocessConfigBeanDefinitions() 中调用 ConfigurationClassBeanDefinitionReaderloadBeanDefinitionsForBeanMethod() 方法实现的。代码如下:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {// 省略代码// Parse each @Configuration classConfigurationClassParser 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());// 省略代码
}

然后在 ConfigurationClassBeanDefinitionReaderloadBeanDefinitionsForBeanMethod() 方法中从 @Bean 注解中获取 initMethoddestroyMethod 这些信息,然后注册 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 接口,如果是则会调用 DeferredImportSelectorHandlerhandle() 方法进行处理。代码如下:

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();}}
}

DeferredImportSelectorHandlerhandle 方法只是先把当前类加入到自己的 deferredImportSelectors 属性中。代码如下:

private class DeferredImportSelectorHandler {@Nullableprivate 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);}}
}

最后在 ConfigurationClassParserparse() 方法最后调用它的 process() 方法。在 DeferredImportSelectorHandlerprocess() 方法中又调用了 DeferredImportSelectorHolderprocessGroupImport() 方法。代码如下:

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);}// 省略代码});}}
}

然后调用到了 AutoConfigurationGroupprocess() 方法,在该方法中会调用 AutoConfigurationImportSelectorgetAutoConfigurationEntry() 方法,这个里这个类就是通过 @EnableAutoConfiguration 引入的类了。代码如下:

private static final class AutoConfigurationGroupimplements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {@Overridepublic 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);}}
}

AutoConfigurationImportSelectorgetAutoConfigurationEntry() 方法调用 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.importsString 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 文件中内容如下:
image.png

这里以 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 {}
http://www.dtcms.com/a/346775.html

相关文章:

  • PostgreSQL15——查询详解
  • 【51单片机】【protues仿真】基于51单片机宠物投食器系统
  • Qt图像裁剪实时显示尺寸实现
  • Qt5 高级功能
  • 当 AI 学会 “理解” 人类:自然语言处理的进化与伦理边界
  • 商品与股指类ETF期权买卖五档Tick分钟级历史行情数据分析
  • 【KO】前端面试三
  • GPT-5:天变了吗?还是风停了?
  • 基于Python的农作物病虫害防治网站 Python+Django+Vue.js
  • MySQL奔溃,InnoDB文件损坏修复记录
  • [2025CVPR-目标检测方向]PointSR:用于无人机视图物体检测的自正则化点监控
  • 尤弥尔传奇能够进行挂机搬砖吗?
  • AI实现超级客户端打印 支持APP 网页 小程序 调用本地客户端打印
  • 爬小红书图片软件:根据搜索关键词,采集笔记图片、正文、评论等
  • Angular初学者入门第三课——工厂函数(精品)
  • 游戏广告投放数据分析项目:拆解投放的“流量密码”
  • kail的浏览器连接不上网
  • 20250823给荣品RD-RK3588开发板刷Rockchip原厂的Buildroot【linux-5.10】时调通AP6275P的WIFI【源码部分】
  • 从 M4S 到 MP4:用 FFmpeg 轻松合并音视频文件
  • 达梦数据库统计信息收集
  • 无人机光伏巡检误检率↓79%!陌讯多模态融合算法在组件缺陷检测的落地优化
  • 【85页PPT】数字化转型LIMS大型企业智能制造之LIMS实验室管理系统产品解决方案(附下载方式)
  • webrtc弱网-SendSideBandwidthEstimation类源码分析与算法原理
  • 使用dism++备份系统时,出现“句柄无效”错误的解决方法
  • 重构实训生态:旅游管理虚拟仿真实训室的标准落地与价值创新
  • 某电器5G智慧工厂网络建设全解析
  • 【ABAP4】创建Package
  • 数字经济、全球化与5G催生域名新价值的逻辑与实践路径
  • Autosar CAN开发06(CAN通讯开发需求-CAN矩阵)
  • 突破传统文本切片的瓶颈:AntSK-FileChunk语义切片技术详解前言:为什么我们需要重新思考文本切片?