刚做后端开发的时候,最早接触的是基础的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.factoriesEnableAutoConfiguration相关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,可以在使用方用@EnableAutoConfigurationexclude属性进行排除。

本人免费整理了Java高级资料,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G,需要自己领取。
传送门:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q

Spring Boot @EnableAutoConfiguration解析的更多相关文章

  1. Spring Boot @EnableAutoConfiguration和 @Configuration的区别

    Spring Boot @EnableAutoConfiguration和 @Configuration的区别 在Spring Boot中,我们会使用@SpringBootApplication来开启 ...

  2. Spring Boot Redis 解析

    redis使用示例 本示例主要内容 使用lettuce操作redis redis字符串存储(RedisStringController.java) redis对象存储(RedisObjectContr ...

  3. Spring Boot AOP解析

    Spring Boot AOP 面向切面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP). OOP中模块化的关键单元是类,而在AOP中,模块化单元是方面. AOP(Aspec ...

  4. spring boot 原理解析一(spring boot 基础特征)

    spring boot 提供了完整的介绍 文档:https://docs.spring.io/spring-boot/docs/2.2.2.RELEASE/reference/html/documen ...

  5. spring boot EnableAutoConfiguration exclude 无效

    本文链接:https://blog.csdn.net/ID19870510/article/details/79373386 首先讲一下SpringBootApplication注解源码定义为 @Ta ...

  6. spring boot定时任务解析

    在SpringBoot中定时任务一般使用的是@Scheduled注解. @Scheduled 1.注解内容: @Target({ElementType.METHOD, ElementType.ANNO ...

  7. 【转载】Spring boot学习记录(三)-启动原理解析

    前言:本系列文章非本人原创,转自:http://tengj.top/2017/04/24/springboot0/ 正文 我们开发任何一个Spring Boot项目,都会用到如下的启动类 @Sprin ...

  8. Spring Boot入门,源码解析

    目录 1.Spring Boot简介 2.微服务 3.Spring Boot HelloWorld 3.1 创建一个Maven工程 3.2 导入依赖Spring Boot相关的依赖 3.3 编写一个主 ...

  9. 【Spring Boot源码分析】@EnableAutoConfiguration注解(一)@AutoConfigurationImportSelector注解的处理

    Java及Spring Boot新手,首次尝试源码分析,欢迎指正! 一.概述 @EnableAutoConfiguration注解是Spring Boot中配置自动装载的总开关.本文将从@Enable ...

随机推荐

  1. JS基础语法---练习:交换两个变量的值

    * JavaScript简称为JS * JavaScript是什么?     * 是一门脚本语言:不需要编译,直接运行     * 是一门解释性的语言:遇到一样代码就解释一行代码     * C#语言 ...

  2. HTTP:Web的基础

    一.HTTP:Web的基础 1.URI 每个Web服务器资源都有一个名字,这样客户端就可以说明它们感兴趣的资源是什么了.服务器资源名称被称为统一资源标识符(URI) URI有两种形式,分别为URL和U ...

  3. iOS中session和cookie的使用

    获取session的方法: #pragma mark - 获取session -(NSString *)getsession{ NSHTTPCookieStorage *cookieStorage = ...

  4. 记一次接口调试错误: {"timestamp":"2019-09-11T03:04:30.036+0000","status":500,"error":"Internal Server Error","message":"Could not write JSON: Object is null; nested exception is com.fasterxml.jackson

    接口测试中用postman测试返回是正常的,但是使用其他人去调用就出错了,找了半天,才想起来使用了nginx,用于端口的代理转发.然后根据错误信息发现json格式的某个字段为null,结合日志中的报文 ...

  5. 【poj3207】Ikki's Story IV - Panda's Trick(2-sat)

    传送门 题意: 给出一个圆,圆上有\(n\)个点,依次为\(0,1,\cdots,n-1\). 现在要连接\(m\)对点,每次连接时可以直接从里面连,也可以从外面连. 最后问,连完这\(m\)对点后, ...

  6. 【洛谷P4589】[TJOI2018]智力竞赛(二分+最小链覆盖)

    洛谷 题意: 给出一个\(DAG\),现在要选出\(n+1\)条可相交的链来覆盖,最终使得未被覆盖的点集中,权值最小的点的权值最大. 思路: 显然最终的答案具有单调性,故直接二分答案来判断: 直接将小 ...

  7. 5. Vue - 小清单实例

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. SpringCloud学习笔记(九、SpringCloud Stream)

    目录: 什么是SpringCloud Stream 如何使用SpringCloud Stream 消息分流 什么是SpringCloud Stream: SpringCloud Stream是一个用于 ...

  9. centos7.6离线安装mysql5.7(附下载链接)

    本来打算直接用原生yum源安装,但是跨国访问网络太慢,只好采用离线安装的方式,原理就是把所需的rpm下载下来再上传服务器安装. 1.rpm文件下载地址: 目录: http://repo.mysql.c ...

  10. STL ——map、set、unordered_map、unordered_set

    1.map和set map和set底层实现均是红黑树 map支持下标操作,set不支持下标操作. set的迭代器是const的,不允许修改元素的值:map允许修改value,但不允许修改key. se ...