Spring Boot @EnableAutoConfiguration解析
刚做后端开发的时候,最早接触的是基础的spring,为了引用二方包提供bean,还需要在xml中增加对应的包<context:component-scan base-package="xxx" />
或者增加注解@ComponentScan({ "xxx"})
。当时觉得挺urgly的,但也没有去研究有没有更好的方式。
直到接触Spring Boot 后,发现其可以自动引入二方包的bean。不过一直没有看这块的实现原理。直到最近面试的时候被问到。所以就看了下实现逻辑。
使用姿势
讲原理前先说下使用姿势。
在project A中定义一个bean。
- package com.wangzhi;
- import org.springframework.stereotype.Service;
- @Service
- public class Dog {
- }
并在该project的resources/META-INF/
下创建一个叫spring.factories
的文件,该文件内容如下
- org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.wangzhi.Dog
然后在project B中引用project A的jar包。
projectA代码如下:
- package com.wangzhi.springbootdemo;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.context.ConfigurableApplicationContext;
- import org.springframework.context.annotation.ComponentScan;
- @EnableAutoConfiguration
- public class SpringBootDemoApplication {
- public static void main(String[] args) {
- ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoApplication.class, args);
- System.out.println(context.getBean(com.wangzhi.Dog.class));
- }
- }
打印结果:
- com.wangzhi.Dog@3148f668
原理解析
总体分为两个部分:一是收集所有spring.factories
中EnableAutoConfiguration
相关bean的类,二是将得到的类注册到spring容器中。
收集bean定义类
在spring容器启动时,会调用到AutoConfigurationImportSelector#getAutoConfigurationEntry
- protected AutoConfigurationEntry getAutoConfigurationEntry(
- AutoConfigurationMetadata autoConfigurationMetadata,
- AnnotationMetadata annotationMetadata) {
- if (!isEnabled(annotationMetadata)) {
- return EMPTY_ENTRY;
- }
- // EnableAutoConfiguration注解的属性:exclude,excludeName等
- AnnotationAttributes attributes = getAttributes(annotationMetadata);
- // 得到所有的Configurations
- List<String> configurations = getCandidateConfigurations(annotationMetadata,
- attributes);
- // 去重
- configurations = removeDuplicates(configurations);
- // 删除掉exclude中指定的类
- Set<String> exclusions = getExclusions(annotationMetadata, attributes);
- checkExcludedClasses(configurations, exclusions);
- configurations.removeAll(exclusions);
- configurations = filter(configurations, autoConfigurationMetadata);
- fireAutoConfigurationImportEvents(configurations, exclusions);
- return new AutoConfigurationEntry(configurations, exclusions);
- }
- getCandidateConfigurations会调用到方法loadFactoryNames:
- public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
- // factoryClassName为org.springframework.boot.autoconfigure.EnableAutoConfiguration
- String factoryClassName = factoryClass.getName();
- // 该方法返回的是所有spring.factories文件中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类路径
- return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
- }
- public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
- private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
- MultiValueMap<String, String> result = cache.get(classLoader);
- if (result != null) {
- return result;
- }
- try {
- // 找到所有的"META-INF/spring.factories"
- Enumeration<URL> urls = (classLoader != null ?
- classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
- ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
- result = new LinkedMultiValueMap<>();
- while (urls.hasMoreElements()) {
- URL url = urls.nextElement();
- UrlResource resource = new UrlResource(url);
- // 读取文件内容,properties类似于HashMap,包含了属性的key和value
- Properties properties = PropertiesLoaderUtils.loadProperties(resource);
- for (Map.Entry<?, ?> entry : properties.entrySet()) {
- String factoryClassName = ((String) entry.getKey()).trim();
- // 属性文件中可以用','分割多个value
- for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
- result.add(factoryClassName, factoryName.trim());
- }
- }
- }
- cache.put(classLoader, result);
- return result;
- }
- catch (IOException ex) {
- throw new IllegalArgumentException("Unable to load factories from location [" +
- FACTORIES_RESOURCE_LOCATION + "]", ex);
- }
- }
注册到容器
在上面的流程中得到了所有在spring.factories
中指定的bean的类路径,在processGroupImports
方法中会以处理@import注解一样的逻辑将其导入进容器。
- public void processGroupImports() {
- for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
- // getImports即上面得到的所有类路径的封装
- grouping.getImports().forEach(entry -> {
- ConfigurationClass configurationClass = this.configurationClasses.get(
- entry.getMetadata());
- try {
- // 和处理@Import注解一样
- processImports(configurationClass, asSourceClass(configurationClass),
- asSourceClasses(entry.getImportClassName()), false);
- }
- catch (BeanDefinitionStoreException ex) {
- throw ex;
- }
- catch (Throwable ex) {
- throw new BeanDefinitionStoreException(
- "Failed to process import candidates for configuration class [" +
- configurationClass.getMetadata().getClassName() + "]", ex);
- }
- });
- }
- }
- private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
- Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
- ...
- // 遍历收集到的类路径
- for (SourceClass candidate : importCandidates) {
- ...
- //如果candidate是ImportSelector或ImportBeanDefinitionRegistrar类型其处理逻辑会不一样,这里不关注
- // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
- // process it as an @Configuration class
- this.importStack.registerImport(
- currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
- // 当作 @Configuration 处理
- processConfigurationClass(candidate.asConfigClass(configClass));
- ...
- }
- ...
- }
可以看到,在第一步收集的bean类定义,最终会被以Configuration
一样的处理方式注册到容器中。
End
@EnableAutoConfiguration
注解简化了导入了二方包bean的成本。提供一个二方包给其他应用使用,只需要在二方包里将对外暴露的bean定义在spring.factories
中就好了。对于不需要的bean,可以在使用方用@EnableAutoConfiguration
的exclude
属性进行排除。
本人免费整理了Java高级资料,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G,需要自己领取。
传送门:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q
Spring Boot @EnableAutoConfiguration解析的更多相关文章
- Spring Boot @EnableAutoConfiguration和 @Configuration的区别
Spring Boot @EnableAutoConfiguration和 @Configuration的区别 在Spring Boot中,我们会使用@SpringBootApplication来开启 ...
- Spring Boot Redis 解析
redis使用示例 本示例主要内容 使用lettuce操作redis redis字符串存储(RedisStringController.java) redis对象存储(RedisObjectContr ...
- Spring Boot AOP解析
Spring Boot AOP 面向切面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP). OOP中模块化的关键单元是类,而在AOP中,模块化单元是方面. AOP(Aspec ...
- spring boot 原理解析一(spring boot 基础特征)
spring boot 提供了完整的介绍 文档:https://docs.spring.io/spring-boot/docs/2.2.2.RELEASE/reference/html/documen ...
- spring boot EnableAutoConfiguration exclude 无效
本文链接:https://blog.csdn.net/ID19870510/article/details/79373386 首先讲一下SpringBootApplication注解源码定义为 @Ta ...
- spring boot定时任务解析
在SpringBoot中定时任务一般使用的是@Scheduled注解. @Scheduled 1.注解内容: @Target({ElementType.METHOD, ElementType.ANNO ...
- 【转载】Spring boot学习记录(三)-启动原理解析
前言:本系列文章非本人原创,转自:http://tengj.top/2017/04/24/springboot0/ 正文 我们开发任何一个Spring Boot项目,都会用到如下的启动类 @Sprin ...
- Spring Boot入门,源码解析
目录 1.Spring Boot简介 2.微服务 3.Spring Boot HelloWorld 3.1 创建一个Maven工程 3.2 导入依赖Spring Boot相关的依赖 3.3 编写一个主 ...
- 【Spring Boot源码分析】@EnableAutoConfiguration注解(一)@AutoConfigurationImportSelector注解的处理
Java及Spring Boot新手,首次尝试源码分析,欢迎指正! 一.概述 @EnableAutoConfiguration注解是Spring Boot中配置自动装载的总开关.本文将从@Enable ...
随机推荐
- LayoutSubviews的调用
1.当view被添加到另一个view上时调用 2.布局子控件时调用 3.屏幕旋转的时候调用 4.当view的尺寸大小改变的时候调用
- Python函数作用域和匿名函数
匿名函数的定义 全局变量和局部变量的概念 global(全局变量)和 nonlocal(局部变量) 闭包.递归.回调 匿名函数 匿名函数 lambda 语法规则:lambda 参数 : 表达式 ...
- bootstrap如何去除自带的样式----导航栏中的菜单实现平滑的过渡到对应的菜单区域-------动态跟换模态框中的内容
问题1:如何去除bootstap中css中自带的overflow:hidden这个样式 今天遇见在bootstap中轮播图上的 附带图 片不能够显示出来,图片始终有一部分的高度 被隐藏了 后来通 ...
- 快读&快写模板【附O2优化】
快读&快写模板 快读快写,顾名思义,就是提升输入和输出的速度.在这里简单介绍一下几种输入输出的优劣. C++ cin/cout 输入输出:优点是读入的时候不用管数据类型,也就是说不用背scan ...
- django haystack报错: ModuleNotFoundError: No module named 'blog.whoosh_cn_backend'
在配置django haystack时报错: 解决方案: 将ENGINE的值 改为 这样就可以了.
- QBXT模拟赛1
总结 期望得分:\(100 + 80 + 10 = 190\) 实际得分:\(90 + 80 + 10 = 180\) 这是在清北的第一场考试,也是在清北考的最高的一次了吧..本来以为能拿\(190\ ...
- 1130不允许连接到MySql server
连接远程服务器mysql时,报错: 1130-host ... is not allowed to connect to this MySql server 这是因为默认只让localhost的主机连 ...
- 数据仓库005 - 复习Linux shell命令 - crontab调度 sh脚本 后台执行 软连接
一.crontab调度 对于linux 自带crontab而言, xxx.sh的一般编写格式以#!/bin/bash 解释器开头,可在脚本中加入: date 但是,shell脚本执行 需要 ...
- mysql事务隔离级别与设置
mysql数据库,当且仅当引擎是InnoDB,才支持事务: 1.隔离级别 事务的隔离级别分为:未提交读(read uncommitted).已提交读(read committed).可重复读(repe ...
- HBase的java操作,最新API。(查询指定行、列、插入数据等)
关于HBase环境搭建和HBase的原理架构,请见笔者相关博客. 1.HBase对java有着较优秀的支持,本文将介绍如何使用java操作Hbase. 首先是pom依赖: <dependency ...