浅谈Spring Boot里的EnableAutoConfiguration

2023-12-25 10:04 雾和狼 540

概述

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 服务器