导航
- 一. 什么是自动装配?
- 二. 如何实现自动装配?
- 1. 配置清单在哪里?
- 2. 自动装配实现
- 核心点1: 从META‐INF/spring.factories路径读取配置类清单
- 核心点2: 过滤
- 第一次过滤: 根据EnableAutoConfiguration注解中exclude和excludeName属性
- 第二次过滤: 通过AutoConfigurationImportFilter过滤器
- 3. 总结自动装配的实现原理
还记得以前面试的时候,经常被问道,说一下Spring Boot的自动装配原理?
咋一听,原理? 觉得逼格应该挺高, 很高大上的东西, 等自己真正了解之后, 卧槽, 就那么回事。
Spring Boot版本是2.6.13
一. 什么是自动装配?
基于注解的Spring Boot的自动装配是Spring Boot框架中的一种特性,它允许开发者使用注解来简化和自动化应用程序的配置和装配过程。
通过使用@EnableAutoConfiguration
特定的注解,Spring Boot可以根据应用程序的依赖关系和配置来自动装配和配置一系列的Bean对象和组件。开发者只需要在相应的类或方法上添加特定的注解,Spring Boot就会根据这些注解的配置信息自动完成相应的初始化和装配工作。
简单来说,就是解放开发者的双手, 一切的脏活累活直接交给Spring Boot来完成。
二. 如何实现自动装配?
在介绍自动装配原理之前, 先举一个生活中的简单例子,某天你发工资了, 要犒劳一下自己, 决定去吃一顿火锅, 来到火锅店扫描点餐, 点了一个鸳鸯锅底,并备注要微辣, 还有一些荤菜和素菜之类的。然后服务端打印了你下单的清单,给到后厨,厨师根据你的清单准备锅底和食材, 一切准备好后,服务员端到你的餐桌上, 直接开吃了。
这个例子重点在于菜单, 需要有人写,还需要有人读,; 例子中大厨就是Spring Boot的角色, 顾客就是写Spring Boot的开发人员, 菜单实际上就是Spring Boot自动装配的配置清单, 所以自动装配实际上就是一个实现这个读的步骤 。
接下来结合源码来具体分析:
1. 配置清单在哪里?
首先pom文件中依赖了spring-boot-starter-web
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
其内部实际上又依赖了spring-boot-autoconfigure
, 使用mvn dependency:tree -Dverbose
生成依赖树, 来验证下
这个配置清单就在spring-boot-autoconfigure
的META-INF/spring.factories
文件中
在spring.factories
文件中, 找到org.springframework.boot.autoconfigure.EnableAutoConfiguration
, 下面都是自动装配的配置清单
# Auto Configure 核心配置: 待自动装配的配置清单
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
.......省略.......
配置清单已经有了,已经知道在什么位置了, 那么只需要让Spring Boot来读取就行了!
2. 自动装配实现
首先看Spring Boot 项目的启动类
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
启动类被注解@SpringBootApplication
定义,此注解用于标识该类是Spring Boot的配置类。配置类通常用于定义各种Bean(例如数据源、事务管理器等)以及其他配置(例如扫描路径、是否开启自动配置等)
细看注解@SpringBootApplication
内部构造
@Target(ElementType.TYPE) //表明@SpringBootApplication注解只能定义在类上
@Retention(RetentionPolicy.RUNTIME) //表明当注解标注的类编译以被JVM加载方式保留
@Documented //Java Doc会生成注解信息
@Inherited //是否会被继承
@SpringBootConfiguration //标注在某个类上,表示这是一个Spring Boot的配置类
@EnableAutoConfiguration //★核心, 开启自动配置功能
//@ComponentScan 扫描包, 相当于在spring.xml 配置中<context:comonent-scan>
//但是并没有指定basepackage,如果没有指定spring底层会自动扫描当前配置类所有在的包
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
/**
* 排除特定的自动配置类,使它们永远不会被应用。返回:要排除的类
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
/**
* 排除特定的自动配置类名,使它们永远不会被应用。返回:要排除的类名
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
/**
* 属性可以指定一个或多个包名,用逗号分隔。告诉Spring Boot在启动时需要扫描的包,以查找带有注解的类
* @SpringBootApplication(scanBasePackages = {"com.example.package1", "com.example.package2"})
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
/**
* 属性指定一个或多个类,Spring Boot 在启动时会扫描这些类所在的包
* @SpringBootApplication(scanBasePackageClasses = {MyClass1.class, MyClass2.class})
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
/**
* 该属性指定一个自定义的Bean名称生成器。
* Spring Boot应用程序中的每个Bean都需要一个唯一的名称来标识它们,通常使用默认的命名策略。
* 但是,如果需要对Bean的名称进行自定义,可以通过实现BeanNameGenerator接口并在nameGenerator属性中指定自定义的Bean名称生成器。
* 使用自定义的名称生成器可以根据项目需求为Bean生成特定的名称。
*/
@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
/**
* 该属性指定一个自定义的Bean名称生成器。
* 用于控制是否为@Configuration配置类中的@Bean方法生成代理对象
* 默认情况下,Spring Boot应用程序会为配置类(也就是带有@Configuration注解的类)中的@Bean方法生成代理对象。
* 这样做的目的是为了确保@Bean方法每次调用都返回同一个Bean实例,以充分利用Spring的缓存机制。
* 但是,如果希望禁用这种代理行为,可以将proxyBeanMethods属性设置为false。
* 禁用代理行为可能会导致@Bean方法每次都创建一个新的Bean实例,可能会降低应用程序的性能。
*/
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
可以看到,注解类SpringBootApplication
又被@EnableAutoConfiguration
注解定义, 这么直白的名字,一看就知道它要开启自动装配
,SpringBoot要开始骚了,于是默默进去看源码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //用于指定自动配置类所在的包。它的作用是告诉Spring Boot应该扫描哪个包来寻找自动配置类
@Import(AutoConfigurationImportSelector.class) //核心
public @interface EnableAutoConfiguration {
/**
* 一个常量,用于指定一个环境变量或系统属性,用于在启用自动配置时覆盖默认值。
* 默认情况下,自动配置是启用的,但可以通过设置这个属性来覆盖默值。如果设置了该属性的值为false,则自动配置将被禁用。
*/
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* 用于排除某些自动配置类。可以指定一个或多个类,使其不被自动配置
* 例如:@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
*/
Class<?>[] exclude() default {};
/**
* 与 exclude 属性类似,但是通过指定类的全限定名来排除自动配置类
* 例如:@EnableAutoConfiguration(excludeName"org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration")
*/
String[] excludeName() default {};
}
利用注解@Import
导入了AutoConfigurationImportSelector
类, 而AutoConfigurationImportSelector
类实现了DeferredImportSelector
,并重写了getImportGroup()方法
@Override
public Class<? extends Group> getImportGroup() {
return AutoConfigurationGroup.class;
}
返回了一个自定义实现DeferredImportSelector.Group
类的AutoConfigurationGroup
静态内部类, 并重写process()
方法
private static class AutoConfigurationGroup
implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();
private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();
private ClassLoader beanClassLoader;
private BeanFactory beanFactory;
private ResourceLoader resourceLoader;
private AutoConfigurationMetadata autoConfigurationMetadata;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
//核心方法getAutoConfigurationEntry(),加载配置类清单
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
........省略......
}
Spring Boot内部在解析@Import注解时会调用AutoConfigurationImportSelector.getAutoConfigurationEntry()
方法,
下面是Spring Boot 2.6.13
版本的源码: 重点关注三个核心点
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//核心点1: 从META‐INF/spring.factories中获得候选的自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
//去重
configurations = removeDuplicates(configurations);
//核心点2:过滤
//第一次过滤:根据EnableAutoConfiguration注解中exclude和excludeName属性,获取不需要自动装配的类名单
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
//进行排除
checkExcludedClasses(configurations, exclusions);
//exclusions 也排除
configurations.removeAll(exclusions);
//第二次过滤: 通过读取spring.factories 中的OnBeanCondition\OnClassCondition\OnWebApplicationCondition进行过滤
configurations = getConfigurationClassFilter().filter(configurations);
//这个方法是调用实现了AutoConfigurationImportListener的bean.. 分别把候选的配置名单,和排除的配置名单传进去做扩展
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
核心点1: 从META‐INF/spring.factories路径读取配置类清单
在SpringFactoriesLoader
中的loadSpringFactories()
方法中有明确定义
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
//指定了路径
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
MultiValueMap<String, String> result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Map.Entry<?, ?> entry = (Map.Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
spring.factories
配置清单中的内容是不是全部读?
不是的, 根据标识符org.springframework.boot.autoconfigure.EnableAutoConfiguration
, 从指定位置读取内容。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//从META‐INF/spring.factories中获得候选的自动配置类
//读取的配置类有133个
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
在源码SpringFactoriesLoader.loadFactoryNames()
方法中有定义
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//从META‐INF/spring.factories中获得候选的自动配置类
//读取的配置类有133个
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
/**
* 依据getSpringFactoriesLoaderFactoryClass()可知, 参数factoryType其实就是EnableAutoConfiguration.class
*/
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
//factoryTypeName的值就是org.springframework.boot.autoconfigure.EnableAutoConfiguration
//刚好对应上spring.factories配置清单上指定位置
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
核心点2: 过滤
为什么需要过滤? 从spring.factories文件中读取到所有配置类,是否都需要进行自动装配,然后让如IOC容器
中?
答案是否定的, 并不是都需要进行自动装配, 需要按需装配, 这就需要Spring Boot自己进行选择性的判断。
根据源码AutoConfigurationImportSelector.getAutoConfigurationEntry()
方法可知,会进行两次过滤
第一次过滤: 根据EnableAutoConfiguration注解中exclude和excludeName属性
根据EnableAutoConfiguration
注解中exclude
和excludeName
属性,获取不需要自动装配的类名单, 但是从注解SpringBootApplication类中可知, 定义的@EnableAutoConfiguration
注解并没有设置exclude和excludeName属性值,所以可忽略
第二次过滤: 通过AutoConfigurationImportFilter过滤器
首先会在spring.factories
文件根据org.springframework.boot.autoconfigure.AutoConfigurationImportFilter
标识, 在指定位置读取需要进行过滤的内容
# Auto Configuration Import Filters 核心过滤: 根据条件进行许选择性的自动装配
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
使用过滤器AutoConfigurationImportFilter
, 争对三个spring.factories文件定义的内容分别对注解@OnBeanCondition,
@OnClassCondition
和@OnWebApplicationCondition
进行条件按判断, 不符合条件的不会进行自动装配。
见源码SpringFactoriesLoader
中的loadFactories()
方法
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryType, "'factoryType' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
//factoryType 是org.springframework.boot.autoconfigure.AutoConfigurationImportFilter
//从spring.factories文件的org.springframework.boot.autoconfigure.AutoConfigurationImportFilter指定位置读取内容
//集合中有三个
//org.springframework.boot.autoconfigure.condition.OnBeanCondition
//org.springframework.boot.autoconfigure.condition.OnClassCondition
//org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
}
List<T> result = new ArrayList(factoryImplementationNames.size());
Iterator var5 = factoryImplementationNames.iterator();
while(var5.hasNext()) {
String factoryImplementationName = (String)var5.next();
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
最后过滤完成后, 剩下的就是需要进行自动装配的配置类了
拿HttpEncodingAutoConfiguration
配置类,来举例
//标记了@Configuration Spring底层会给配置创建cglib动态代理。 作用:就是防止每次调用本类的Bean方法而重新创建对象,Bean是默认单例的
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ServerProperties.class) //启用可以在配置类设置的属性 对应的类
//条件注解, 当前是web环境,条件成立,才进行自动装配
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
//条件注解,容器中必须有CharacterEncodingFilter类,条件成立,才进行自动装配
@ConditionalOnClass(CharacterEncodingFilter.class)
//条件注解, 表示配置文件application.properties有server.servlet.encoding配置,且定义的值是enabled时,条件成立,允许进行自动装配
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
private final Encoding properties;
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();
}
@Bean
@ConditionalOnMissingBean // 当IOC容器中没有CharacterEncodingFilter时,才会创建
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}
@Bean
public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new LocaleCharsetMappingsCustomizer(this.properties);
}
......省略......
}
当注解@ConditionalOnWebApplication
, @ConditionalOnClass
和@ConditionalOnProperty
定义的条件都成立时, 那么Spring Boot就会对该配置类里被注解@Bean定义的方法,进行自动装配, 创建对应的Bean对象,然后放入IOC容器中。这样当Spring Boot正常运行后,就可以直接从IOC容器中拿Bean对象直接使用了。
3. 总结自动装配的实现原理
从从spring-boot-autoconfigure JAR包的META-INF/spring.factories文件中获取的配置列表起初有133个
经过层层过滤之后, 最后还剩下24个
最后结合例子和源码详解,Spring Boot基于注解的自动装配的原理,是不是很简单。