Spring Boot 自动配置原理

Author: ACatSmiling

Since: 2024-07-23

核心场景启动器

Spring Boot 的每个场景启动器都引入了一个spring-boot-starter,这是 Spring Boot 的核心场景启动器。

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

本文代码基于 Spring Boot 3.2.4 版本。

在 spring-boot-starter 中,又引入了spring-boot-autoconfigure包,spring-boot-autoconfigure 里面事先定义了所有场景的所有配置,只要这个包下的所有类都能生效,那么相当于 Spring Boot 官方写好的整合功能就生效了。

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>

但是 Spring Boot 默认只扫描主程序所在的包,因此扫描不到 spring-boot-autoconfigure 下定义好的所有配置类,这部分的工作,是由@EnableAutoConfiguration注解完成的。

引导加载自动配置类

主程序:

@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}

@SpringBootApplication

@SpringBootApplication

@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 {
}

@SpringBootConfiguration

@SpringBootConfiguration:是@Configuration的派生注解,表明当前主类实际上也是一个配置类。

@ComponentScan

@ComponentScan:指定扫描的包,默认为当前主类所在包及其子包。

@EnableAutoConfiguration

@EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
@AutoConfigurationPackage

@AutoConfigurationPackage

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
@Import(AutoConfigurationPackages.Registrar.class)

向容器中注册了一个 AutoConfigurationPackages.Registrar.class 组件:

/**
* {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
* configuration.
*/
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
  • new PackageImports(metadata).getPackageNames():拿到元注解所包含的包信息,实际上就是主类所在的包,如 cn.zero.cloud.business。
  • register()的功能,也就是将主类所在包下的所有组件,批量注册到容器中,这也就是默认包路径为主类所在包的原因。
@Import(AutoConfigurationImportSelector.class)

向容器中注册了一个 AutoConfigurationImportSelector.class 组件,执行如下方法:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

执行getAutoConfigurationEntry(annotationMetadata)方法,向容器中批量注册一些组件:

/**
* Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
* of the importing {@link Configuration @Configuration} class.
* @param annotationMetadata the annotation metadata of the configuration class
* @return the auto-configurations that should be imported
*/
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}

执行getCandidateConfigurations(annotationMetadata, attributes)方法:

/**
* Return the auto-configuration class names that should be considered. By default,
* this method will load candidates using {@link ImportCandidates}.
* @param metadata the source metadata
* @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
* attributes}
* @return a list of candidate configurations
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())
.getCandidates();
// 从类路径下获取预先定义的组件,Spring Boot 2 中此文件名为 spring.factories
Assert.notEmpty(configurations,
"No auto configuration classes found in "
+ "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}

Spring Boot 3 中,事先定义的组件位于 spring-boot-autoconfigure 包类路径下的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,在 Spring Boot 2 中,对应的文件名是 spring.factories。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
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;
}

执行ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())方法,获取类路径下配置文件中事先定义的,所有待批量注册的组件(配置类):

/**
* Loads the names of import candidates from the classpath.
*
* The names of the import candidates are stored in files named
* {@code META-INF/spring/full-qualified-annotation-name.imports} on the classpath.
* Every line contains the full qualified name of the candidate class. Comments are
* supported using the # character.
* @param annotation annotation to load
* @param classLoader class loader to use for loading
* @return list of names of annotated classes
*/
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
Assert.notNull(annotation, "'annotation' must not be null");
ClassLoader classLoaderToUse = decideClassloader(classLoader);
String 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);
}
image-20240723221554851

按需开启自动配置项

在上面的分析中,Spring Boot 在启动时,默认会加载 152 个自动配置的组件。但在实际启动时,各 xxxxAutoConfiguration 组件,会根据@Conditional注解,即按照条件装配规则,实现按需配置。我们选取如下几个组件进行分析。

AopAutoConfiguration

org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,其装配规则如下:

package org.springframework.boot.autoconfigure.aop;
import org.aspectj.weaver.Advice;
import org.springframework.aop.config.AopConfigUtils;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} for Spring's AOP support. Equivalent to enabling
* {@link EnableAspectJAutoProxy @EnableAspectJAutoProxy} in your configuration.
* <p>
* The configuration will not be activated if {@literal spring.aop.auto=false}. The
* {@literal proxyTargetClass} attribute will be {@literal true}, by default, but can be
* overridden by specifying {@literal spring.aop.proxy-target-class=false}.
*
* @author Dave Syer
* @author Josh Long
* @since 1.0.0
* @see EnableAspectJAutoProxy
*/
@AutoConfiguration
// 当配置文件中配置了 spring.aop.auto 属性,且值为 true 时,AopAutoConfiguration 生效,默认为 true
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
// 当 org.aspectj.weaver.Advice.class 文件存在时,AspectJAutoProxyingConfiguration 才生效
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration {
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
static class JdkDynamicAutoProxyConfiguration {
}
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class CglibAutoProxyConfiguration {
}
}
// 当 org.aspectj.weaver.Advice.class 文件不存在,且配置文件中 spring.aop.proxy-target-class 属性值为 true (默认为 true)时,ClassProxyingConfiguration 生效
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class ClassProxyingConfiguration {
@Bean
static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying() {
return (beanFactory) -> {
if (beanFactory instanceof BeanDefinitionRegistry registry) {
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
};
}
}
}

可以看出,当导入 spring-boot-starter-aop 依赖时,会注册 AspectJAutoProxyingConfiguration 配置类,否则,注册 ClassProxyingConfiguration 配置类,后者是 Spring Boot 默认的的 AOP 功能。

DispatcherServletAutoConfiguration

org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,其装配规则如下:

package org.springframework.boot.autoconfigure.web.servlet;
import java.util.Arrays;
import java.util.List;
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.ServletRegistration;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.servlet.DispatcherServlet;
/**
* {@link EnableAutoConfiguration Auto-configuration} for the Spring
* {@link DispatcherServlet}. Should work for a standalone application where an embedded
* web server is already present and also for a deployable application using
* {@link SpringBootServletInitializer}.
*
* @author Phillip Webb
* @author Dave Syer
* @author Stephane Nicoll
* @author Brian Clozel
* @since 2.0.0
*/
// 当前配置类的配置顺序
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
// 在 ServletWebServerFactoryAutoConfiguration 后配置
@AutoConfiguration(after = ServletWebServerFactoryAutoConfiguration.class)
// 当项目是一个原生的 Web Servlet 应用时
@ConditionalOnWebApplication(type = Type.SERVLET)
// 当容器中存在 DispatcherServlet 时
@ConditionalOnClass(DispatcherServlet.class)
public class DispatcherServletAutoConfiguration {
/**
* The bean name for a DispatcherServlet that will be mapped to the root URL "/".
*/
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
/**
* The bean name for a ServletRegistrationBean for the DispatcherServlet "/".
*/
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
// 当容器中存在 ServletRegistration 时
@ConditionalOnClass(ServletRegistration.class)
// 开启 WebMvcProperties 类的配置绑定功能,并注册到容器中
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
// 注册 DispatcherServlet 组件到容器中,名字为 dispatcherServlet
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
// 新建了一个 DispatcherServlet 对象
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
configureThrowExceptionIfNoHandlerFound(webMvcProperties, dispatcherServlet);
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
@SuppressWarnings({ "deprecation", "removal" })
private void configureThrowExceptionIfNoHandlerFound(WebMvcProperties webMvcProperties,
DispatcherServlet dispatcherServlet) {
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
}
// 注册 MultipartResolver 组件到容器中,即文件上传解析器
@Bean
// 当容器中存在 MultipartResolver 时
@ConditionalOnBean(MultipartResolver.class)
// 当容器中没有 name 为 multipartResolver 的 MultipartResolver 对象时
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// 当容器中有 MultipartResolver 的对象,resolver 参数会自动绑定该对象
// 此方法的作用是,防止有些用户配置的文件上传解析器不符合规范:
// 将用户自己配置的文件上传解析器重新注册给容器,并重命名为 multipartResolver (方法名)
// (Spring Boot 中的文件上传解析器的名字,就叫 multipartResolver)
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
}
...
}
  • @ConditionalOnWebApplication(type = Type.SERVLET):Spring Boot 支持两种类型的 Web 应用开发,一种是响应式,一种是原生 Servlet。响应式 Web 开发需要导入spring-boot-starter-webflux依赖,原生 Servlet Web 开发需要导入spring-boot-starter-web依赖。

  • @ConditionalOnClass(DispatcherServlet.class):在主类中可以验证项目中存在 DispatcherServlet 类。

    @SpringBootApplication
    public class MainApplication {
    public static void main(String[] args) {
    ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
    String[] beanNamesForType = run.getBeanNamesForType(DispatcherServlet.class);
    System.out.println(beanNamesForType.length);// 1
    }
    }

HttpEncodingAutoConfiguration

org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,其装配规则如下:

package org.springframework.boot.autoconfigure.web.servlet;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.boot.web.servlet.server.Encoding;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.web.filter.CharacterEncodingFilter;
/**
* {@link EnableAutoConfiguration Auto-configuration} for configuring the encoding to use
* in web applications.
*
* @author Stephane Nicoll
* @author Brian Clozel
* @since 2.0.0
*/
@AutoConfiguration
// 开启 ServerProperties 类的配置绑定功能,并注册到容器中
@EnableConfigurationProperties(ServerProperties.class)
// 当项目是一个原生的 Web Servlet 应用时
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
// 当容器中存在 CharacterEncodingFilter 时
@ConditionalOnClass(CharacterEncodingFilter.class)
// 当配置文件中 server.servlet.encoding 属性值为 enabled (默认为 true) 时
@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();
}
// 向容器中注册一个 CharacterEncodingFilter 组件,此组件就是解决 Spring Boot 收到的请求出现乱码的问题
@Bean
// 当容器中没有这个 Bean 时才配置,即用户未配置时,Spring Boot 才主动配置一个
@ConditionalOnMissingBean
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);
}
static class LocaleCharsetMappingsCustomizer
implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
private final Encoding properties;
LocaleCharsetMappingsCustomizer(Encoding properties) {
this.properties = properties;
}
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
if (this.properties.getMapping() != null) {
factory.setLocaleCharsetMappings(this.properties.getMapping());
}
}
@Override
public int getOrder() {
return 0;
}
}
}

HttpEncodingAutoConfiguration 配置类,可以防止 Spring Boot 乱码。

修改默认配置

一般来说,Spring Boot 默认会在底层配好所有需要的组件,但是如果用户自己配置了,就会以用户配置的优先。

以 CharacterEncodingFilter 为例,如果用户希望按自己的需求进行配置,可以在配置类中自行添加:

@Configuration
public class MyConfig {
@Bean
public CharacterEncodingFilter characterEncodingFilter() {
// filter的实现代码
}
}

从前面对 HttpEncodingAutoConfiguration 的分析可以看出,当用户自己配置了 CharacterEncodingFilter 的实例时,Spring Boot 就不会再配置。

当然,也可以根据组件 @ConfigurationProperties 注解绑定的属性,按需做相应的修改。

总结

Spring Boot 先加载所有默认的自动配置类,即 xxxxxAutoConfiguration.class,每个自动配置类按照条件进行生效。xxxxxAutoConfiguration.class 在配置时,会从对应的 xxxxxProperties.class 中取值,而 xxxxxProperties.class 会和配置文件中对应的值进行绑定(@EnableConfigurationProperties 注解)。

  • 生效的配置类,会给容器中装配很多不同功能的组件。
  • 这些组件装配到容器中后,项目就具有了该组件所具有的功能。
  • 如果用户自行配置了某一个组件,则以用户配置的优先。

若想实现定制化配置,有两种方法:

  • 方法一:用户自行配置组件,添加@Bean注解,用以替换 Spring Boot 底层的默认组件。
  • 方法二:用户查看该组件从配置文件种获取的是什么属性的值,然后按需求自行修改对应的属性值。比如 HttpEncodingAutoConfiguration 对应的就是配置文件中的server.servlet.encoding属性。

更多配置项取值,参考:https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties

自动配置过程:xxxxxAutoConfiguration.class ---> 注册组件 ---> 从 xxxxxProperties.class 里面拿值 ----> 绑定 application.properties 文件。可以看出,一般通过修改 application.properties 文件中相应的配置,就可完成 Spring Boot 功能的修改。

原文链接

https://github.com/ACatSmiling/zero-to-zero/blob/main/SpringEcosystem/spring-boot.md

posted @   ACatSmiling  阅读(101)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示