从 SpringBoot 启动流程了解自动配置原理

Spring 和 SpringBoot 简介

spring.png

Spring 和 SpringBoot 应该是所有 Java 开发们最熟悉的开源框架,首先了解一下 Spring 的诞生和特点。

Spring

Spring 是 Java EE 编程领域的一个轻量级设计层面框架,该框架由一个叫 Rod Johnson 的程序员在 2002 年最早提出并随后创建,于2004年3月24日,发布了1.0正式版。

它解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用。

在整个框架中,各类型的功能被抽象成一个个的 Bean,这样就可以实现各种功能的管理,包括控制反转(IOC)和面向切面(AOP)编程。

  • IOC:
    应用了 IOC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。它的底层设计模式采用了工厂模式,所有的 Bean 都需要注册到Bean工厂中,将其初始化和生命周期的监控交由工厂实现管理。
    “不用你找,我来提供给你”,这就是控制反转的含义。
  • AOP:

    AOP 主要针对模块之间的交叉关注点进行模块化,通过分离应用的业务逻辑与系统级服务(例如日志(logging)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。

SpringBoot

既然有了 Spring 那为什么还需要 SpringBoot 呢?因为 Spring 虽然提供了 IOC、AOP 等能力,但使用 Spring 进行项目开发需要在配置文件中写很多代码,所有这些配置工作都是浪费时间的行为。

因此,Pivotal 团队于 2013 年设计开发出了 SpringBoot 框架,2014年4月,发布第一个 SpringBoot版本。框架的核心理念是:

Convention Over Configuration (约定优于配置)

SpringBoot 特点:

  • 开箱即用,根据项目依赖自动配置;
  • 功能强大的服务体系,如:嵌入式服务、安全、性能指标、健康检查;
  • 绝无代码生成,可以不需要任何 xml 配置(使用Java配置和注解来代替);
  • 对第三方技术几乎完美整合;
  • 独立运行,内嵌了各种 servlet 容器(Tomcat、Jetty等),不需要打包成 war 包部署到容器中,直接打包成 jar 即可运行。

SpringBoot 约定优于配置的体现主要是:

  1. maven的目录结构:
    a) 默认有resources文件夹存放配置文件
    b) 默认打包方式为jar,要使用jar -jar的方式直接运行和要直接 Main 启动 Spring,需要在 pom文件中加入spring-boot-maven-plugin插件
  2. spring-boot-starter-web 中默认包含 spring mvc 相关 依赖以及内置的tomcat容器,使得构建一个web 应用更加简单;
  3. 默认提供application.properties/yml文件;
  4. 默认通过 spring.profiles.active 属性来决定运行环境时读取的配置文件;
  5. @EnableAutoConfiguration 默认对于依赖的 starter进行自动装载。

SpringBoot 启动关键过程分析

下面从源码(SpringBoot 2.6.15 & Spring 5.3.27)角度深入 SpringBoot 启动流程,介绍其自动配置的原理,以最简单的 SpringBoot 启动类为例:

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
  1. 进入静态方法 run()
public static void main(String[] args) throws Exception {
        run(new Class[0], args);
}

// run 最终进入下面的方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return (new SpringApplication(primarySources)).run(args);
}
  1. 实例化SpringApplication
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        this.bootstrapRegistryInitializers = new ArrayList<>(
                getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }
  1. 确认当前容器的类型,默认的是最常见的SERVLET
static WebApplicationType deduceFromClasspath() {
        if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
                && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        for (String className : SERVLET_INDICATOR_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }
  1. 回到 SpringApplication 构造函数,其多次调用了 getSpringFactoriesInstances(Class<T> type) 方法,该方法中会通过 loadSpringFactories 来获取配置在 spring.factories 文件中的类,并在返回后进行加载、实例化对应的类
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = getClassLoader();
        // Use names and ensure unique to protect against duplicates
        Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
}

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 {
// FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
            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;
    }

小结:自此我们可以了解到为什么我们配置在 META-INF/spring.factories 中的类名何时被读取进容器的缓存中。

  1. SpringApplication 实例化完成后,进入run() 方法
public ConfigurableApplicationContext run(String... args) {
        long startTime = System.nanoTime();
        DefaultBootstrapContext bootstrapContext = createBootstrapContext();
        ConfigurableApplicationContext context = null;
        configureHeadlessProperty();
        // 从spring.factories 中获取运行监听器并启动(starting)
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting(bootstrapContext, this.mainApplicationClass);
        try {
            // 在命令行下启动应用带的参数
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 准备系统环境
            ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            configureIgnoreBeanInfo(environment);
            // 打印 banner
            Banner printedBanner = printBanner(environment);
            // 根据 webApplicationType 调用工厂类创建应用程序上下文容器
            context = createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            // 刷新容器
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
            }
            listeners.started(context, timeTakenToStartup);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, listeners);
            throw new IllegalStateException(ex);
        }
        try {
            Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
            listeners.ready(context, timeTakenToReady);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }
  1. prepareEnvironment 就是读取环境变量或参数或配置中的 spring.profiles.active 来确定环境,之后会加载默认配置文件(application.properties/application.yml 等等)和对应的系统环境配置文件(如 application-dev.properties 等等),在加载顺序上(相同路径下):yml > properties,由于后加载的会覆盖先加载的配置,所以同样的配置优先级:properties> yml

    (代码太多就不贴了)

  2. 之后进入createApplicationContext() (本文主要关注 SpringBoot 自动配置相关原理,所以这里抽几个方法介绍)。

// 从 createApplicationContext() 会进入
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
        try {
            return getFromSpringFactories(webApplicationType, ApplicationContextFactory::create,
                    AnnotationConfigApplicationContext::new);
        }
        catch (Exception ex) {
            throw new IllegalStateException("Unable create a default ApplicationContext instance, "
                    + "you may need a custom ApplicationContextFactory", ex);
        }
}

private <T> T getFromSpringFactories(WebApplicationType webApplicationType,
            BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> defaultResult) {
        for (ApplicationContextFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class,
                getClass().getClassLoader())) {
            T result = action.apply(candidate, webApplicationType);
            if (result != null) {
                return result;
            }
        }
        return (defaultResult != null) ? defaultResult.get() : null;
}
  1. 这里的 getFromSpringFactories 即通过 SpringFactoriesLoader.loadFactories 初始化 SpringBoot 配置的 ApplicationContextFactory 实现:

Untitled

这里 webApplicationType **为 SERVLET,所以这里返回的是 org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext.Factory

public AnnotationConfigServletWebServerApplicationContext() {
        this.reader = new AnnotatedBeanDefinitionReader(this);
        this.scanner = new ClassPathBeanDefinitionScanner(this);
}

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
            BeanDefinitionRegistry registry, @Nullable Object source) {
    ....
        if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
            RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
            def.setSource(source);
            // 这里注册的是 BeanDefinition 还不是 bean
            beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
        }
    ...
    }
  1. AnnotatedBeanDefinitionReader 构造函数中会注册一个重要的 BeanFactoryPostProcessor实现 **ConfigurationClassPostProcessor 这是实现自动配置的重点,下文还会继续介绍。

Untitled

  1. 接下来看一下 refreshContext() 方法,熟悉 Spring 源码的同学对这里的代码应该印象深刻。
// AbstractApplicationContext

@Override
public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

            // 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);

                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                // Invoke factory processors registered as beans in the context.
                // ⭐️ 关注这里
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);
                beanPostProcess.end();

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
                contextRefresh.end();
            }
        }
    }
  1. 重点关注 invokeBeanFactoryPostProcessors() ,这个方法旨在调用BeanDefinitionRegistryPostProcessor **接口的实现,前面提到的ConfigurationClassPostProcessor 就是其中一个实现类。
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)) {
                // ConfigurationClassPostProcessor 实现了 PriorityOrdered 和 BeanDefinitionRegistryPostProcessor
                // 通过 beanFactory.getBean() 初始化了 ConfigurationClassPostProcessor
                    currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    processedBeans.add(ppName);
                }
            }
            sortPostProcessors(currentRegistryProcessors, beanFactory);
            registryProcessors.addAll(currentRegistryProcessors);
            // 调用 ConfigurationClassPostProcessor 的 postProcessBeanDefinitionRegistry 方法
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
            currentRegistryProcessors.clear();
  1. 接下来,在 ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry方法中,会从 beanDefinitionMap 容器中获取标记了 *@*Configuration 注解的类对应的beanDefinition 集合,并且调用 ConfigurationClassParser#parse() 进行解析(显然这里会取到 DemoApplicationbeanDefinition)。
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();
    }
  1. 解析过程在下面 doProcessConfigurationClass() (根据Spring 命名风格,doXXX() 都是真正进行逻辑处理的代码),下面可以看到多个注解的解析逻辑 @Component@PropertySource@ComponentScan@Import@ImportResource@Bean,并且还会递归处理当前类的父类。
protected final SourceClass doProcessConfigurationClass(
            ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
            throws IOException {

        if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
            // Recursively process any member (nested) classes first
            processMemberClasses(configClass, sourceClass, filter);
        }

        // Process any @PropertySource annotations
        for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), PropertySources.class,
                org.springframework.context.annotation.PropertySource.class)) {
            if (this.environment instanceof ConfigurableEnvironment) {
                processPropertySource(propertySource);
            }
            else {
                logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                        "]. Reason: Environment must implement ConfigurableEnvironment");
            }
        }

        // Process any @ComponentScan annotations
        Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
        if (!componentScans.isEmpty() &&
                !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
            for (AnnotationAttributes componentScan : componentScans) {
                // The config class is annotated with @ComponentScan -> perform the scan immediately
                Set<BeanDefinitionHolder> scannedBeanDefinitions =
                        this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
                // Check the set of scanned definitions for any further config classes and parse recursively if needed
                for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                    BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                    if (bdCand == null) {
                        bdCand = holder.getBeanDefinition();
                    }
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                        parse(bdCand.getBeanClassName(), holder.getBeanName());
                    }
                }
            }
        }

        // Process any @Import annotations
        processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

        // Process any @ImportResource annotations
        AnnotationAttributes importResource =
                AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
        if (importResource != null) {
            String[] resources = importResource.getStringArray("locations");
            Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
            for (String resource : resources) {
                String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
                configClass.addImportedResource(resolvedResource, readerClass);
            }
        }

        // Process individual @Bean methods
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
        for (MethodMetadata methodMetadata : beanMethods) {
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }

        // Process default methods on interfaces
        processInterfaces(configClass, sourceClass);

        // Process superclass, if any
        if (sourceClass.getMetadata().hasSuperClass()) {
            String superclass = sourceClass.getMetadata().getSuperClassName();
            if (superclass != null && !superclass.startsWith("java") &&
                    !this.knownSuperclasses.containsKey(superclass)) {
                this.knownSuperclasses.put(superclass, configClass);
                // Superclass found, return its annotation metadata and recurse
                return sourceClass.getSuperClass();
            }
        }

        // No superclass -> processing is complete
        return null;
    }
  1. 这里重点关注 @Import 注解的解析
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
            boolean checkForCircularImports) {

        if (importCandidates.isEmpty()) {
            return;
        }

        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }
        else {
            this.importStack.push(configClass);
            try {
                for (SourceClass candidate : importCandidates) {
                // 1. ImportSelector 的实现
                    if (candidate.isAssignable(ImportSelector.class)) {
                        // Candidate class is an ImportSelector -> delegate to it to determine imports
                        Class<?> candidateClass = candidate.loadClass();
                        ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
                                this.environment, this.resourceLoader, this.registry);
                        Predicate<String> selectorFilter = selector.getExclusionFilter();
                        if (selectorFilter != null) {
                            exclusionFilter = exclusionFilter.or(selectorFilter);
                        }
                        if (selector instanceof DeferredImportSelector) {
                        // 实现 DeferredImportSelector 表示延迟导入,会在当前方法调用完成后进行处理
                            this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                        }
                        else {
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                            processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
                        }
                    }
                    // 2. ImportBeanDefinitionRegistrar 的实现
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        // Candidate class is an ImportBeanDefinitionRegistrar ->
                        // delegate to it to register additional bean definitions
                        Class<?> candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar =
                                ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                                        this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    }
                    else {
                        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                        // process it as an @Configuration class
                        // 3. 普通类,当做 @Configuration 处理
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
                    }
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                        configClass.getMetadata().getClassName() + "]", ex);
            }
            finally {
                this.importStack.pop();
            }
        }
    }

以*@*EnableAutoConfiguration 引入的AutoConfigurationImportSelector为例,实现了ImportSelectorDeferredImportSelector接口。则代码会走到第一个 if 分支,进行类加载、实例化并调用各种 Aware 接口。

  1. 对于 AutoConfigurationImportSelector 最终会在下面的 process 方法进行解析
    1. 第一个参数 annotationMetadata 代表了当前正在解析的(即 *@*Configuration 标注的)类,也就是 DemoApplication
    2. 第二个参数 deferredImportSelector@Import 引入的 AutoConfigurationImportSelector 实例
// AutoConfigurationImportSelector

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()));
            AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
                    .getAutoConfigurationEntry(annotationMetadata);
            this.autoConfigurationEntries.add(autoConfigurationEntry);
            for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                this.entries.putIfAbsent(importClassName, annotationMetadata);
            }
}
  1. 其中通过从 spring.factories 获取 EnableAutoConfiguration 的实现类类名是完成自动化配置的关键所在。
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        // 获取 EnableAutoConfiguration 配置的参数
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 获取 spring.factories 中声明的 key 为 EnableAutoConfiguration 的所有实现类名
        // (包括自定义的 jar 包中的配置)
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        // 根据条件过滤出需要的 EnableAutoConfiguration 实现
        configurations = getConfigurationClassFilter().filter(configurations);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

比如笔者自定义的 Starter,在 jar 包中定义了:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
  com.ray.middleware.log.autoconfigure.LogAutoConfiguration,
  com.ray.middleware.log.interceptor.LogAspectAutoConfiguration

在上述 getCandidateConfigurations 方法中也能够读取到。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// getSpringFactoriesLoaderFactoryClass() = EnableAutoConfiguration.class
        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;
    }
  1. 在解析完成后,这些被自动加载的类会被解析为 bd,加入到 IOC 容器中
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
....
        do {
            StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
            parser.parse(candidates);
            parser.validate();

            Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
            configClasses.removeAll(alreadyParsed);

            // Read the model and create bean definitions based on its content
            if (this.reader == null) {
                this.reader = new ConfigurationClassBeanDefinitionReader(
                        registry, this.sourceExtractor, this.resourceLoader, this.environment,
                        this.importBeanNameGenerator, parser.getImportRegistry());
            }
            // 解析为 bd 注册到 BeanDefinitionMap
            this.reader.loadBeanDefinitions(configClasses);
            alreadyParsed.addAll(configClasses);
            processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();

            candidates.clear();
            ...
    }
  1. 这些 EnableAutoConfiguration 实现类最终通过 ConfigurationClassBeanDefinitionReader#registerBeanDefinitionForImportedConfigurationClass 注册到 beanDefinitionMap
private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
        AnnotationMetadata metadata = configClass.getMetadata();
        AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);

        ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
        configBeanDef.setScope(scopeMetadata.getScopeName());
        String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
        AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);

        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
        definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
        // 加入 IOC 容器
        this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
        configClass.setBeanName(configBeanName);

        if (logger.isTraceEnabled()) {
            logger.trace("Registered bean definition for imported class '" + configBeanName + "'");
        }
    }
  1. 后续就是按照 Spring 对于 BD 进行解析完成 Bean 正常的生命周期的过程了,笔者就不继续往下写了。

    小结:自此可以看到对于 SpringBoot 自动配置重要的注解@EnableAutoConfiguration ,其实该注解本身作用并不大,真正重要的是内部的 @Import 注解。

其实观察所有 @Enable* 开头的注解(例如 @EnableWebMvc@EnableDubbo …),会发现里面都存在@Import ,例如:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import(DubboConfigConfigurationRegistrar.class)
public @interface EnableDubboConfig {

    /**
     * It indicates whether binding to multiple Spring Beans.
     *
     * @return the default value is <code>false</code>
     * @revised 2.5.9
     */
    boolean multiple() default true;

}

另外 @Import 必须(直接或间接)和 @Configuration 注解放在一起时才会生效,原因在上面源码也可以看到,由于ConfigurationClassPostProcessor ****负责 @Import 注解的解析,

总结

@Enable* 注解的作用

SpringBoot 中 @Enable* 其实是作为组合注解存在,本身作用不大,真正核心的是 @Import 注解。既然这样何不直接用 @Import ?这里体现了框架设计者对于组件高内聚低耦合的设计。以**@**EnableAutoConfiguration 为例:

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

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};

}
  1. @Enable* 就是用来启用某一个功能的配置。启用某一功能,仅需要加上一个注解即可生效,可以使组建之间的相互依赖降低了耦合性。
  2. @Enable* 可以带上一些配置参数,在解析导入类时,可以从这里拿到自定义配置的参数。
  3. 除了 @Import注解还有其它的注解 @AutoConfigurationPackage ,它可以组合多个注解放到一起。
  4. 进行封装后方便开发者使用,如果不进行封装,那么启用这个功能可能需要更多的类加载,还有要其它注解去配合,那对开发者来说,配置起来又相对麻烦了许多,将其包装到一起,只需要记住使用这一功能记住这个注解即可。

自动配置原理总结

SpringBoot 的自动配置其实就是利用 @Import 导入的 AutoConfigurationImportSelector 类在起的作用。

核心逻辑是读取所有 jar 包下在 classpath 路径下的 META-INF/spring.factories 文件,获取配置的 org.springframework.boot.autoconfigure.EnableAutoConfiguration 配置的所有实现类的全类名,另外再使用 @Conditional 等方式过滤出真正需要的类进行加载,注入 IOC 容器,接下来这些配置的类就自然生效可以使用了。

参考

spring.io

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

Scroll to Top