刚做后端开发的时候,最早接触的是基础的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. CSS3 新增选择器

    CSS3 新增选择器 结构(位置)伪类选择器(CSS3) :first-child :选取属于其父元素的首个子元素的指定选择器 :last-child :选取属于其父元素的最后一个子元素的指定选择器 ...

  2. 4-1-JS数据类型及相关操作

    js的数据类型 判断数据类型 用typeof   typeof "John"                 // alert(typeof "John") 返 ...

  3. 多个浏览器下应用前端JS实现一键导出excel表

    自己试验了几种方法,找到一种较为全面的一种方式一键输出Excel表格,代码如下 <!DOCTYPE html> <html> <head lang="en&qu ...

  4. Python的4个内置数据结构

    Python提供了4个内置数据结构(内置指可以直接使用,无需先导入),可以保存任何对象集合,分别是列表.元组.字典和集合. 一.列表有序的可变对象集合. 1.列表的创建例子 list1 = []lis ...

  5. IAR调试查看程序时间

    在无仿真器情况下设置: 调试仿真,点击view菜单下registers 软件仿真时计算两断点CYCLECOUNTER(在CPU registers中)的差值,乘以指令周期(MCLK)便是执行时间

  6. 如何使用DOM4j+xpath 解析

    1.首先去dom4j官网下载一个对应版本的JAR包 XML的配置文件 导入一个config.xml文件 下面是dom4j+xpath解析XML文件

  7. golang中的struct标签tag

    这个以前用beego时涉及过,时间久了,就忘了. 现在k8s里的controller,一样用了这个语法, 再拾起来吧. http://www.01happy.com/golang-struct-tag ...

  8. 汇编和C/C++的混合编程方式

    常用的有三种方式: 在C/C++代码中嵌入汇编指令 汇编调用C/C++ C/C++中调用汇编 实例一: 在C中嵌入汇编: void string_copy(char *dst,const char * ...

  9. scanf函数和cin的区别、类的数组、C++排序函数

    给定n个字符串,将这n个字符串按照字典序进行排列,此处用排列函数是C++的库函数sort,产生如下两个疑问,望大佬解答 #include <iostream> #include <a ...

  10. 七,专著研读(Logistic回归)

    七,专著研读(Logistic回归) 分类:k-近邻算法,决策树,朴素贝叶斯,Logistic回归,支持向量机,AdaBoost算法. 运用 k-近邻算法,使用距离计算来实现分类 决策树,构建直观的树 ...