概述
Spring Boot 以其通过提供合理的默认值和自动化配置来简化 Spring 应用程序开发的能力而闻名。它的关键功能之一是自动配置,这减少了设置 Spring 应用程序所需的样板代码。
自动装配根据类路径中存在的依赖项自动配置应用程序。旨在通过为数据源、安全性和 Web 服务器等组件提供默认配置来简化 Spring 应用程序的设置。
自动装配的全路径为 org.springframework.boot.autoconfigure.EnableAutoConfiguration
实现
Spring Boot 在应用程序启动期间扫描类路径(Spring SPI)并检查是否存在特定的库或类。如果找到匹配的依赖项,它会自动使用默认设置配置相应的组件。此过程由带有 @Conditional 注解的 @Configuration 类驱动,该注解控制着是否满足自动装配条件。下面介绍两个在实现自动装配的重要的概念:
Spring SPI
Spring SPI 是对Java SPI 的增强,Spring 通过 spring.handlers 和 spring.factories 两种方式实现 SPI 机制,可以在不修改 Spring 源码的前提下,做到对 Spring 框架的扩展开发。
文件默认所在位置: resources/META-INF 下。
spring.handlers
自定义标签配置文件, Spring 在 2.0 时便引入了 spring.handlers,通过配置 spring.handlers 文件实现自定义标签并使用自定义标签解析类进行解析实现动态扩。
自定义标签的解析核心类 org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver. 负责解析 spring.handlers 配置文件, 生成 namespaceUri 和 NamespaceHandler 名称的映射.核心代码如下:
@Override
@Nullable
public NamespaceHandler resolve(String namespaceUri) {
//handlerMappings 存放 namespaceUri 和 namespaceHandler 之间的对应关系
Map<String, Object> handlerMappings = getHandlerMappings();
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
String className = (String) handlerOrClassName;
try {
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
"] for namespace [" + namespaceUri + "]", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
className + "] for namespace [" + namespaceUri + "]", err);
}
}
}
spring.handlers的配置如下:
http://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
看到这个,同学们是不是很熟悉的感觉,就是之前写 Spring 的 XML 的配置文件时候,引入的 bean,context 等, 如下所示
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->
<bean id="foo" class="x.y.Foo">
<meta key="cacheName" value="foo"/>
<property name="name" value="Rick"/>
</bean>
<bean id="myField"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</bean></beans>
实现主要在 spring-beans 这个模块中
Spring.factories
spring.factories 实现的 SPI 和 Java SPI 很相似,不过 Java SPI 一个接口一个文件,文件名是接口的全限定名称;Spring 的实现是所有接口都在文件 spring.facrories 文件中, 多个实现用逗号隔开。
Spring SPI 的通过类 org.springframework.core.io.support.SpringFactoriesLoader 实现,核心逻辑如下
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryType, "'factoryType' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
}
List<T> result = new ArrayList<>(factoryImplementationNames.size());
for (String factoryImplementationName : factoryImplementationNames) {
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
Conditional
加载某个Bean时候需要满足的条件,一般可见将满足的条件分为两类:
- 创建的 @Bean 需要依赖某些属性(properties)
- 创建的 @Bean 依赖某些类信息(libraries on classpath)
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
Condition 是一个接口,它公开一种名为“matches”的方法,该方法返回 true/false:是否满足条件
package org.springframework.context.annotation;
import org.springframework.core.type.AnnotatedTypeMetadata;
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
@Conditional 属于比较低层的底层的注解,Spring Boot 附带了自己的一组附加 @Conditional 注释, 覆盖大部分场景
@ConditionalOnBean
@ConditionalOnClass
@ConditionalOnCloudPlatform
@ConditionalOnExpression
@ConditionalOnJava
@ConditionalOnJndi
@ConditionalOnMissingBean
@ConditionalOnMissingClass
@ConditionalOnNotWebApplication
@ConditionalOnProperty
@ConditionalOnResource
@ConditionalOnSingleCandidate
@ConditionalOnWebApplication
SpringBoot 实现 AutoConfiguration
SpringBoot 如何知道要从每个starter 的 spring.factories 中找呢,了解上面的Spring SPI 相关技术, 下面通过Springboot 源码来看一下自动装配被装载过程:
1 在启动 SpringBootApplication 后, 源码如下:
//org.springframework.boot.SpringApplication#run
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
//...... 代码省略
refreshContext(context);
//...... 代码省略
}
2 在创建完SpringBoot的环境变量后,事件注册等,下面开始创建容器,读过Spring源码的, 看到refreshContext方法应该很亲切, 接着看具体的 refreshContext 方法的链路。
//org.springframework.boot.SpringApplication#refreshContext
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
//org.springframework.boot.SpringApplication#refresh
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
3 可以看到最终还是调用了 ApplicationContext 容器的 refresh 方法, 接着便是开始初始化 Spring 容器了。
//org.springframework.context.support.AbstractApplicationContext#refresh
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
//......
}catch (BeansException ex) {
//......
}
}
4 这时候就开始初始化 BeanFactoryPostProcessors, 了解 Spring 的应该知道这个接口是对实现容器的扩展,比如读取配置,并将这些属性填充到对应的 Bean中。这个方法的具体实现是通过 org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors 来实现的。
//org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
}
5 接下来看一下 org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors 的处理过程, 这个方法很复杂,只关心配置如何注册和解析的。通过 org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors 注册 BeanDefinitionRegistryPostProcessor,具体的注册调用BeanDefinitionRegistryPostProcessor具体的实现。
//org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory,
List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
//......
List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
}
//org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors
/**
Invoke the given BeanDefinitionRegistryPostProcessor beans.
*/
private static void invokeBeanDefinitionRegistryPostProcessors(
Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanDefinitionRegistry(registry);
}
}
6 这里面最重要的就是 BeanDefinitionRegistryPostProcessor 这个接口, 这个接口读取项目中的 BeanDefinition 之后执行,提供补充的借口,动态注册自己的 BeanDefinition , 特别是 classpath 之外的Bean。 它是对 BeanFactoryPostProcessor 这个接口的扩展,而且它在 BeanFactoryPostProcessor 之前执行。它的主要实现是 org.springframework.context.annotation.ConfigurationClassPostProcessor .
//org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry
/**
Derive further bean definitions from the configuration classes in the registry.
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);
processConfigBeanDefinitions(registry);
}
7 看到具体的处理是调用 org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions 来处理的,主要是注册的验证和解析,下面对该方法的部分实现
//org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
/**
Build and validate a configuration model based on the registry of
{@link Configuration} classes.
*/
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
//....
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
parser.parse(candidates);
parser.validate();
//......
}
8 接下来是看一下是 org.springframework.context.annotation.ConfigurationClassParser 如何解析的,
//org.springframework.context.annotation.ConfigurationClassParser#parse
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
this.deferredImportSelectorHandler.process();
}
//org.springframework.context.annotation.ConfigurationClassParser#parse
protected final void parse(Class<?> clazz, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(clazz, beanName));
}
9 处理 @Configuration通过 processConfigurationClass 来处理的,看一下具体的实现
//org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
// ......
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
//.......
}
10 可以看到具体处理的 doProcessConfigurationClass 这个方法才是处理 @Configuration 核心逻辑
//org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
//.....
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
//.....
}
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
//......
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(
configClass, (DeferredImportSelector) selector);
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
//......
}
11 自动装配是通过 org.springframework.boot.autoconfigure.AutoConfigurationImportSelector ,查找所有以这个引入的配置, 看一下 selectImports的 实现, 在查找AutoConfiguration的 是通过 Spring 自定义的 SPI 实现(SpringFactoriesLoader), 在 SpringFactoriesLoader 中定义了从 META-INF/spring.factories 找 EnableAutoConfiguration 的实现。
//org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
//org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
//.....
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
//.....
}
//org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations
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;
}
//org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getSpringFactoriesLoaderFactoryClass
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
以上是自动装配的底层实现。
应用
Tomcat 的 AutoConfiguration
Spring Boot 如何默认启动嵌入式 Tomcat 服务器?简单的:
- 需要检查 Tomcat 是否在类路径上。 (@ConditionalOnClass(Tomcat.class))
- 用户配置 tomcat 相关属性,比如端口,超时
- 需要使用 Spring WebMVC 的 DispatcherServlet 并将其注册到 Tomcat,以使 @RestControllers 及其 @GetMappings 和 @PostMappings 工作
- 启动内嵌的 tomcat 服务器