三、自动配置原理入门
目录
3. 自动配置原理
3.1. @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、@EnableAutoConfiguration、@ComponentScan 这三个注解身上。
3.1.1. @SpringBootConfiguration
@SpringBootConfiguration 的源码如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {...}
- 前四个:元注解
- @Configuration:代表 SpringBootConfiguration 是一个配置类
- @Indexed:Spring Framework 5.0 引入的一个注解,旨在为 Spring 的模式注解添加索引,以提高启动性能
3.1.2. @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({Registrar.class})
public @interface AutoConfigurationPackage {...}
Registrar 的源码
/**
* Class for storing auto-configuration packages for reference later (e.g. by JPA entity
* scanner).
*
* @author Phillip Webb
* @author Dave Syer
* @author Oliver Gierke
* @since 1.0.0
*/
public abstract class AutoConfigurationPackages {
...
/**
* {@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));
}
}
...
}
metadata 包含以下的内容:
表明这个注解是标记在 MainApp 这个类上边的。然后通过这个信息获取 MainApp 所在的包名,如下:
综上所述:
- 利用 Registrar 向容器中导入一批组件
- 导入的组件就是主程序所在的包及其子包 (上述示例的 MainApp 所在的包及其子包)
@Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector 的源码:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
...
}
@Import(AutoConfigurationImportSelector.class) 的作用是向容器中导入一批组件。导入的组件就是 autoConfigurationEntry.getConfigurations() 转成为 String 数组后,该数组中的内容。autoConfigurationEntry 是 getAutoConfigurationEntry(annotationMetadata) 的返回值。所以往下走,
getAutoConfigurationEntry(annotationMetadata) 的源码:
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
源码中,getCandidateConfigurations(annotationMetadata, attributes) 的返回值经过
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
去掉一些排除项,并且经过
AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
封装之后的返回结果。所以继续往下走,
getCandidateConfigurations(annotationMetadata, attributes) 的源码:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
会使用 SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()) 加载器加载一些类,从而得到 configurations 。所以继续往下走:
loadFactoryNames 的源码:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
在 loadFactoryNames 中返回的是 loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()) 的结果,所以继续往下走:
loadSpringFactories 的源码:
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
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()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> {
return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
});
cache.put(classLoader, result);
return result;
} catch (IOException var14) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
}
}
}
Enumeration urls = classLoader.getResources(“META-INF/spring.factories”) 会读取 META-INF/spring.factories 文件,从而根据配置文件的内容加载所有的组件。spring.factories 文件的位置:
spring-boot-autoconfigure-2.5.5.jar\META-INF/spring.factories
内容如下:
# 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.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
在这个版本(2.5.5)下应该是 132 个,而且格式都是 XXXAutoConfiguration 。这 132 个在启动时默认全部都有,但是最终会按 @Confitional 的派生类所指定的条件生效(参照 3.2 自动装配流程)。
3.1.3. @ComponentScan
指定要扫描哪些内容。使用 @SpringBootConfiguration 注解了主程序则不需要再使用 @ComponentScan 去指定扫描的范围,因为会自动扫描主程序所在的包及其子包。
3.2. 自动配置流程
3.2.1. 生效的配置
拿 DispatcherServlet 举例子。
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {...}
- @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE):指定该配置类的优先级
- @Configuration(proxyBeanMethods = false):标明该类是个配置类
- @ConditionalOnWebApplication(type = Type.SERVLET):标明该类是一个 Servlet 类型的 Web 工程(原生类型:Spring MVC)
- 与之对应的就是 spring-boot 的响应式编程:导入的是 Spring WebFlux
- @ConditionalOnClass(DispatcherServlet.class):存在 DispatcherServlet 这个类,导入 spring MVC 必定会存在
- @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class):标明在 ServletWebServerFactoryAutoConfiguration 配置完成后才进行配置
只有在上述的条件都通过了,下面的才会被执行:
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
@Bean
@ConditionalOnBean(MultipartResolver.class)
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
}
-
Configuration(proxyBeanMethods = false):这是个配置类
-
Conditional(DefaultDispatcherServletCondition.class):通过 DefaultDispatcherServletCondition 判断是否要实例化
-
ConditionalOnClass(ServletRegistration.class):存在 ServletRegistration 类的情况,Tomcat 组件被导入
-
@EnableConfigurationProperties(WebMvcProperties.class):绑定配置文件
-
@ConfigurationProperties( prefix = "spring.mvc" ) public class WebMvcProperties {...}
-
-
dispatcherServlet 方法:实例化一个 DispatcherServlet 并且经过初始设置并且返回,默认名(dispatcherServlet)
-
multipartResolver 方法(文件上传解析器):
- @Bean:声明为组件
- @ConditionalOnBean(MultipartResolver.class):如果容器中存在这种类型的组件,则返回
- @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME):不存在名字为 multipartResolver 的组件
- 给 @Bean 标记的方法传递参数的话,那么这个参数的值是会从容器中相应的组件返回
这三个注解是为了担心用户自定义的文件上传解析器不符合规范。
3.2.2. 不生效的配置
拿 Aop 举例子。
有些包没有导进来,就会导致这些配置不会生效。
这个是需要导入以下的内容,也就是需要导入 aspectj 相关的依赖:
import org.aspectj.weaver.Advice;
3.2.3. 自定义配置
方式一:
使用 @Bean 替换底层组件。
方式二:
去修改组件的配置文件的默认值,以达到预期的效果。
方式二的示例:将编码格式指定为 UTF-8
-
查看编码格式的底层组件绑定配置文件的前缀
-
@Configuration( proxyBeanMethods = false ) @EnableConfigurationProperties({ServerProperties.class}) @ConditionalOnWebApplication( type = Type.SERVLET ) @ConditionalOnClass({CharacterEncodingFilter.class}) @ConditionalOnProperty( prefix = "server.servlet.encoding", value = {"enabled"}, matchIfMissing = true ) public class HttpEncodingAutoConfiguration {
-
查官方文档,所有的都列出来了
-
-
修改配置文件
resources\application.properties
# encoding format server.servlet.encoding.charset=UTF-8