【Spring Boot实战】源码解析Spring Boot自动配置原理

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

一、简介

Spring致力于让Java开发更简单,SpringBoot致力于让使用Spring进行Java开发更简单,SpringCloud致力于基于SpringBoot构建微服务生态圈,让微服务开发更简单。随着这几年spring官网的更新可有看出spring发展的roadmap

20191102100684\_1.png

网上有一个spring发展的时间线,也可以看下

20191102100684\_2.png

随着近几年微服务的火爆,SpringBoot及SpringCloud被使用的越来越多,了解其内部原理显然越来越重要。

二、SpringBoot简介

Spring Boot将很多魔法带入了Spring应用程序的开发之中,其中最重要的是以下四个核心。
 自动配置:针对很多Spring应用程序常见的应用功能,Spring Boot能自动提供相关配置。
 起步依赖:告诉Spring Boot需要什么功能,它就能引入需要的库。
 命令行界面:这是Spring Boot的可选特性,借此你只需写代码就能完成完整的应用程序,
无需传统项目构建。
 Actuator:让你能够深入运行中的Spring Boot应用程序,一探究竟。

其中自动配置和起步依赖是目前和程序猿密切相关的。后面就重点分析下自动配置和起步依赖。而自动配置又是本篇的重点。

三、正题(基于springboot2.1.1分析)

1、Spring Boot的运行

以Spring Boot集成Dubbo为例,用idea基于Spring Initialzr很方便就能搭建Spring Boot项目结构,看下项目启动引导类

    package com.jtt.hhl;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    @SpringBootApplication
    public class DubboServerApplication {

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

    }

该类主要有两个作用:配置和启动引导。首先,这是主要的Spring配置类。虽然Spring Boot的自动配置免除了很多Spring配置,但你还需要进行少量配置来启用自动配置。@SpringBootApplication注解开启了Spring的组件扫描和Spring Boot的自动配置功能。自动配置就出现了。看下改注解

    package org.springframework.boot.autoconfigure;

    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    import org.springframework.boot.SpringBootConfiguration;
    import org.springframework.boot.context.TypeExcludeFilter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.ComponentScan.Filter;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.FilterType;
    import org.springframework.core.annotation.AliasFor;

    /**
     * Indicates a {@link Configuration configuration} class that declares one or more
     * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
     * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
     * annotation that is equivalent to declaring {@code @Configuration},
     * {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
     *
     * @author Phillip Webb
     * @author Stephane Nicoll
     * @since 1.2.0
     */
    @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 {

        /**
         * Exclude specific auto-configuration classes such that they will never be applied.
         * @return the classes to exclude
         */
        @AliasFor(annotation = EnableAutoConfiguration.class)
        Class<?>[] exclude() default {};

        /**
         * Exclude specific auto-configuration class names such that they will never be
         * applied.
         * @return the class names to exclude
         * @since 1.3.0
         */
        @AliasFor(annotation = EnableAutoConfiguration.class)
        String[] excludeName() default {};

        /**
         * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
         * for a type-safe alternative to String-based package names.
         * @return base packages to scan
         * @since 1.3.0
         */
        @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
        String[] scanBasePackages() default {};

        /**
         * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
         * scan for annotated components. The package of each class specified will be scanned.
         * <p>
         * Consider creating a special no-op marker class or interface in each package that
         * serves no purpose other than being referenced by this attribute.
         * @return base packages to scan
         * @since 1.3.0
         */
        @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
        Class<?>[] scanBasePackageClasses() default {};

    }

实际上@SpringBootApplication 将三个有用的注解组合在了一起,看其注释也很明确
 Spring的 @Configuration :标明该类使用Spring基于Java的配置。Java配置相当于spring的xml配置。
 Spring的 @ComponentScan :启用组件扫描,这样你写的Web控制器类和其他组件才能被自动发现并注册为Spring应用程序上下文里的Bean。
 Spring Boot 的 @EnableAutoConfiguration : 这 个 不 起 眼 的 小 注 解 也 可 以 称 为@Abracadabra就是这一行配置开启了Spring Boot自动配置的魔力,让你不用再写成篇的配置了。

关于前两个注解可以参考前两遍文章【Spring实战】—-Spring配置文件的解析【Spring实战】Spring注解配置工作原理源码解析,本文的分析重点是注解@EnableAutoConfiguration,接下来先看引导类的启动引导功能。

要部署Spring Boot应用程序有几种方式,其中包含传统的WAR文件部署。但这里的 main() 方法让你可以在命令行里把该应
用程序当作一个可执行JAR文件来运行。来看下SpringApplication.run(DubboServerApplication.class, args);

    /**
         * Static helper that can be used to run a {@link SpringApplication} from the
         * specified sources using default settings and user supplied arguments.
         * @param primarySources the primary sources to load
         * @param args the application arguments (usually passed from a Java main method)
         * @return the running {@link ApplicationContext}
         */
        public static ConfigurableApplicationContext run(Class<?>[] primarySources,
                String[] args) {
            return new SpringApplication(primarySources).run(args);
        }

最终会调用构造函数,primarySources就是DubboServerApplication.class,

    /**
         * Create a new {@link SpringApplication} instance. The application context will load
         * beans from the specified primary sources (see {@link SpringApplication class-level}
         * documentation for details. The instance can be customized before calling
         * {@link #run(String...)}.
         * @param resourceLoader the resource loader to use
         * @param primarySources the primary bean sources
         * @see #run(Class, String[])
         * @see #setSources(Set)
         */
        @SuppressWarnings({ "unchecked", "rawtypes" })
        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();
            setInitializers((Collection) getSpringFactoriesInstances(
                    ApplicationContextInitializer.class));
            setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
            this.mainApplicationClass = deduceMainApplicationClass();
        }

然后调用run方法

    /**
         * Run the Spring application, creating and refreshing a new
         * {@link ApplicationContext}.
         * @param args the application arguments (usually passed from a Java main method)
         * @return a running {@link ApplicationContext}
         */
        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 {
                ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                        args);
                ConfigurableEnvironment environment = prepareEnvironment(listeners,
                        applicationArguments);
                configureIgnoreBeanInfo(environment);
                Banner printedBanner = printBanner(environment);
                context = createApplicationContext();
                exceptionReporters = getSpringFactoriesInstances(
                        SpringBootExceptionReporter.class,
                        new Class[] { ConfigurableApplicationContext.class }, context);
                prepareContext(context, environment, listeners, applicationArguments,
                        printedBanner);
                refreshContext(context);
                afterRefresh(context, applicationArguments);
                stopWatch.stop();
                if (this.logStartupInfo) {
                    new StartupInfoLogger(this.mainApplicationClass)
                            .logStarted(getApplicationLog(), stopWatch);
                }
                listeners.started(context);
                callRunners(context, applicationArguments);
            }
            catch (Throwable ex) {
                handleRunFailure(context, ex, exceptionReporters, listeners);
                throw new IllegalStateException(ex);
            }

            try {
                listeners.running(context);
            }
            catch (Throwable ex) {
                handleRunFailure(context, ex, exceptionReporters, null);
                throw new IllegalStateException(ex);
            }
            return context;
        }

run方法最重要的作用就是创建spring上下文环境,这里根据不用的应用环境创建不同的上下文


    /**
         * The class name of application context that will be used by default for non-web
         * environments.
         */
        public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
                + "annotation.AnnotationConfigApplicationContext";

        /**
         * The class name of application context that will be used by default for web
         * environments.
         */
        public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
                + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

        /**
         * The class name of application context that will be used by default for reactive web
         * environments.
         */
        public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
                + "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";

    /**
         * Strategy method used to create the {@link ApplicationContext}. By default this
         * method will respect any explicitly set application context or application context
         * class before falling back to a suitable default.
         * @return the application context (not yet refreshed)
         * @see #setApplicationContextClass(Class)
         */
        protected ConfigurableApplicationContext createApplicationContext() {
            Class<?> contextClass = this.applicationContextClass;
            if (contextClass == null) {
                try {
                    switch (this.webApplicationType) {
                    case SERVLET:
                        contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                        break;
                    case REACTIVE:
                        contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                        break;
                    default:
                        contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                    }
                }
                catch (ClassNotFoundException ex) {
                    throw new IllegalStateException(
                            "Unable create a default ApplicationContext, "
                                    + "please specify an ApplicationContextClass",
                            ex);
                }
            }
            return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
        }

可以看出三个上下文都是基于注解的,默认的是AnnotationConfigApplicationContext,本例中classpath中没有web、servlet的配置(改判断就是在SpringApplication创建时初始化的),上下文环境就是AnnotationConfigApplicationContext,注意其无参构造函数

/**
* Create a new AnnotationConfigApplicationContext that needs to be populated
* through {@link #register} calls and then manually {@linkplain #refresh refreshed}.
*/
public AnnotationConfigApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}

在this.reader操作中,重大作用的注解处理器已经添加完毕org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)

然后准备上下文环境prepareContext(context, environment, listeners, applicationArguments, printedBanner);主要的工作就是加载引导类bean,本例中就是DubboServerApplication.class,将其注册到beanDefinitionMap中。后面有ConfigurationClassPostProcessor处理器对其处理,解析配置类,相当于使用xml配置时xml的解析(只不过时机不同)。

再看刷新上下文环境refreshContext(context);

    @Override
        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);

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

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

最终调用的AbstractApplicationContext中的refresh方法,是不是很熟悉了。简单说下:

1)、prepareRefresh()刷新前的预处理,属性设置及合法性检验等;

2)、obtainFreshBeanFactory();获取BeanFactory 为DefaultListableBeanFactory(在上下文初始化的时候已经创建);设置id(本例中是spring.application.name的值dubbo-server);

3)、prepareBeanFactory(beanFactory);BeanFactory的预准备工作(BeanFactory进行一些设置);

4)、postProcessBeanFactory(beanFactory);BeanFactory准备工作完成后进行的后置处理工作(提供给子类用的);

5)、invokeBeanFactoryPostProcessors(beanFactory);激活各种BeanFactory处理器,执行BeanFactoryPostProcessor的方法。@Configration注解的处理器ConfigurationClassPostProcessor就是在这里调用的。

6)、registerBeanPostProcessors(beanFactory);注册BeanPostProcessor(Bean的后置处理器)【 intercept bean creation】,这里只是注册,调用的时机是getBean()的时候。

7)、initMessageSource();初始化MessageSource组件(做国际化功能;消息绑定,消息解析);

8)、initApplicationEventMulticaster();初始化事件派发器;

9)、onRefresh();留给子容器(子类)

10)、registerListeners();给容器中将所有项目里面的ApplicationListener注册进来;

11)、finishBeanFactoryInitialization(beanFactory);初始化所有剩下的非懒加载的单实例bean(因为有的再之前使用的时候已经初始化过了,因此这里叫初始化剩下的非懒加载的单实例bean);

12)、finishRefresh();完成BeanFactory的初始化创建工作;IOC容器就创建完成;

至此,spring上下文,也就是IOC容器创建完成了,从最初的XmlBeanFactory、ClassPathXmlApplicationContext再到web应用中的ContextLoaderListener,再到SpringBoot的SpringApplication及@SpringBootApplication注解,其实原理都是一样的:创建容器DefaultListableBeanFactory,注册相关的Bean(在spring中除了容器就是bean,后置处理器就是被当成bean处理的。只不过注解的基础bean注册是基于@SpringBootApplication的@Configuration及@ComponentScan注解,在refresh->invokeBeanFactoryPostProcessors(beanFactory)->ConfigurationClassPostProcessor处理器中ConfigurationClassParser进行解析的,其中由于该启动类还有@ComponentScan注解,因此还会进行组件扫描ComponentScanAnnotationParser,扫描规则为com.jtt.hhl基础包下的,不包含过滤规则@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class,主要是排除自动配置类及自定义配置规则;其他的config类,如本例中的ConfigTest是在扫描的时候处理的,如果是配置类还会再进行一次ConfigurationClassParser解析) 。而基于xml配置文件的是在refresh->obtainFreshBeanFactory()->loadBeanDefinitions解析xml配置文件),创建bean以及后置处理器处理(注意BeanFactoryPostProcessor和BeanPostProcessor处理时机,注解的处理都是在后置处理器中进行的),事件驱动模型。看下ConfigurationClassParser.java中的主要方法:对@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean注解的处理,这也说明了@Configuration注解类中可以使用的注解有哪些。

    /**
         * Apply processing and build a complete {@link ConfigurationClass} by reading the
         * annotations, members and methods from the source class. This method can be called
         * multiple times as relevant sources are discovered.
         * @param configClass the configuration class being build
         * @param sourceClass a source class
         * @return the superclass, or {@code null} if none found or previously processed
         */
        @Nullable
        protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
                throws IOException {

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

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

2、自动配置

上面说了注解@SpringBootApplication中的@Configuration及@ComponentScan注解,下面看一下和自动配置相关的注解@EnableAutoConfiguration,改注解的最终要的作用是导入了@Import(AutoConfigurationImportSelector.class),这个也就是自动配置开启的地方,@Import也是向IOC容器中导入组件的一种方式,引申一下。

    给容器中注册组件;
    * 1)、包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)[自己写的类]
    * 2)、@Bean[导入的第三方包里面的组件]
    * 3)、@Import[快速给容器中导入一个组件]
    *     1)、@Import(要导入到容器中的组件);容器中就会自动注册这个组件,id默认是全类名
    *     2)、ImportSelector:返回需要导入的组件的全类名数组;
    *     3)、ImportBeanDefinitionRegistrar:手动注册bean到容器中

@ComponentScan扫描时针对@Import注解处理,看下AutoConfigurationImportSelector的处理过程:

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

先看一下有意思的getImports(sourceClass)方法,主要是获取@Import注解信息

    /**
         * Returns {@code @Import} class, considering all meta-annotations.
         */
        private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
            Set<SourceClass> imports = new LinkedHashSet<>();
            Set<SourceClass> visited = new LinkedHashSet<>();
            collectImports(sourceClass, imports, visited);
            return imports;
        }
    /**
         * Recursively collect all declared {@code @Import} values. Unlike most
         * meta-annotations it is valid to have several {@code @Import}s declared with
         * different values; the usual process of returning values from the first
         * meta-annotation on a class is not sufficient.
         * <p>For example, it is common for a {@code @Configuration} class to declare direct
         * {@code @Import}s in addition to meta-imports originating from an {@code @Enable}
         * annotation.
         * @param sourceClass the class to search
         * @param imports the imports collected so far
         * @param visited used to track visited classes to prevent infinite recursion
         * @throws IOException if there is any problem reading metadata from the named class
         */
        private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
                throws IOException {

            if (visited.add(sourceClass)) {
                for (SourceClass annotation : sourceClass.getAnnotations()) {
                    String annName = annotation.getMetadata().getClassName();
                    if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
                        collectImports(annotation, imports, visited);
                    }
                }
                imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
            }
        }

本例中,这里的sourceClass就是com.jtt.hhl.DubboServerApplication,最终通过将java注解(@Target、@Retention、@Documented、@Inherited)排除,一层层递归到EnableAutoConfiguration注解的@Import(AutoConfigurationImportSelector.class)注解及@AutoConfigurationPackage中的@Import(AutoConfigurationPackages.Registrar.class)一步步看处理:

    private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
                Collection<SourceClass> importCandidates, 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) {
                        if (candidate.isAssignable(ImportSelector.class)) {
                            // Candidate class is an ImportSelector -> delegate to it to determine imports
                            Class<?> candidateClass = candidate.loadClass();
                            ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                            ParserStrategyUtils.invokeAwareMethods(
                                    selector, this.environment, this.resourceLoader, this.registry);
                            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);
                            }
                        }
                        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 =
                                    BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
                            ParserStrategyUtils.invokeAwareMethods(
                                    registrar, 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
                            this.importStack.registerImport(
                                    currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                            processConfigurationClass(candidate.asConfigClass(configClass));
                        }
                    }
                }
                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();
                }
            }
        }

第一个为(class org.springframework.boot.autoconfigure.AutoConfigurationPackages$Registrar)ImportBeanDefinitionRegistrar,只是将其加入到importBeanDefinitionRegistrars中。

看第二个org.springframework.boot.autoconfigure.AutoConfigurationImportSelector为ImportSelector,又是DeferredImportSelector类型的,因此走进this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);也只将其加入到deferredImportSelectors(延迟的导入选择器)中备用。什么时候用呢,继续往下看,解析完后会调用this.deferredImportSelectorHandler.process();

    public void process() {
                List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
                this.deferredImportSelectors = null;
                try {
                    if (deferredImports != null) {
                        DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
                        deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
                        deferredImports.forEach(handler::register);
                        handler.processGroupImports();
                    }
                }
                finally {
                    this.deferredImportSelectors = new ArrayList<>();
                }
            }

这里取出deferredImportSelectors中的AutoConfigurationImportSelector,开始处理,这里用到了java8的新特性::及lambda表达式,重点关注下handler.processGroupImports();

    public void processGroupImports() {
                for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
                    grouping.getImports().forEach(entry -> {
                        ConfigurationClass configurationClass = this.configurationClasses.get(
                                entry.getMetadata());
                        try {
                            processImports(configurationClass, asSourceClass(configurationClass),
                                    asSourceClasses(entry.getImportClassName()), false);
                        }
                        catch (BeanDefinitionStoreException ex) {
                            throw ex;
                        }
                        catch (Throwable ex) {
                            throw new BeanDefinitionStoreException(
                                    "Failed to process import candidates for configuration class [" +
                                            configurationClass.getMetadata().getClassName() + "]", ex);
                        }
                    });
                }
            }

其中的lambda表达式,重点看,先看getImports()

    /**
             * Return the imports defined by the group.
             * @return each import with its associated configuration class
             */
            public Iterable<Group.Entry> getImports() {
                for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
                    this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                            deferredImport.getImportSelector());
                }
                return this.group.selectImports();
            }
        }

这里又进行了process处理,这里的this.group就是在::register中赋值的

    public void register(DeferredImportSelectorHolder deferredImport) {
                Class<? extends Group> group = deferredImport.getImportSelector()
                        .getImportGroup();
                DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
                        (group != null ? group : deferredImport),
                        key -> new DeferredImportSelectorGrouping(createGroup(group)));
                grouping.add(deferredImport);
                this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
                        deferredImport.getConfigurationClass());
            }

就是AutoConfigurationImportSelector的内部类AutoConfigurationGroup,因此这里的process就是调用内部类的process方法

    @Override
            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(getAutoConfigurationMetadata(),
                                annotationMetadata);
                this.autoConfigurationEntries.add(autoConfigurationEntry);
                for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                    this.entries.putIfAbsent(importClassName, annotationMetadata);
                }
            }

会调用getAutoConfigurationEntry,获取自动配置条目

    /**
         * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
         * of the importing {@link Configuration @Configuration} class.
         * @param autoConfigurationMetadata the auto-configuration metadata
         * @param annotationMetadata the annotation metadata of the configuration class
         * @return the auto-configurations that should be imported
         */
        protected AutoConfigurationEntry getAutoConfigurationEntry(
                AutoConfigurationMetadata autoConfigurationMetadata,
                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 = filter(configurations, autoConfigurationMetadata);
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationEntry(configurations, exclusions);
        }

关键的部分:获取所有的自动配置信息List configurations = getCandidateConfigurations(annotationMetadata, attributes);

    /**
         * Return the auto-configuration class names that should be considered. By default
         * this method will load candidates using {@link SpringFactoriesLoader} with
         * {@link #getSpringFactoriesLoaderFactoryClass()}.
         * @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 = 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;
        }

从哪里获取,classpath中的META-INF/spring.factories文件中,进入SpringFactoriesLoader类的下述方法

    /**
         * The location to look for factories.
         * <p>Can be present in multiple JAR files.
         */
        public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
            MultiValueMap<String, String> result = cache.get(classLoader);
            if (result != null) {
                return result;
            }

            try {
                Enumeration<URL> urls = (classLoader != null ?
                        classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                        ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
                result = new LinkedMultiValueMap<>();
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    for (Map.Entry<?, ?> entry : properties.entrySet()) {
                        String factoryClassName = ((String) entry.getKey()).trim();
                        for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }
                cache.put(classLoader, result);
                return result;
            }
            catch (IOException ex) {
                throw new IllegalArgumentException("Unable to load factories from location [" +
                        FACTORIES_RESOURCE_LOCATION + "]", ex);
            }
        }

实际上该方法在前面调用过(最一开始SpringApplication创建的时候调用setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));其中的getSpringFactoriesInstances方法就调用到了)因此cache中有值,就直接返回了key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的vule,注意这里的map是多值map(MultiValueMap)。看下spring.factories

20191102100684\_3.png

很多地方都有,本例中一共 size = 119个,因此可以定制,看下dubbo中spring.factoreis内容

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration

    org.springframework.context.ApplicationListener=\
    com.alibaba.boot.dubbo.context.event.OverrideDubboConfigApplicationListener,\
    com.alibaba.boot.dubbo.context.event.WelcomeLogoApplicationListener,\
    com.alibaba.boot.dubbo.context.event.AwaitingNonWebApplicationListener

因此这里获取的自动配置信息包含com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration,返回去看getAutoConfigurationEntry方法,会对配置信息进行过滤,去掉重复的removeDuplicates(configurations),去掉扫描排除的,重点看下filter方法

    private List<String> filter(List<String> configurations,
                AutoConfigurationMetadata autoConfigurationMetadata) {
            long startTime = System.nanoTime();
            String[] candidates = StringUtils.toStringArray(configurations);
            boolean[] skip = new boolean[candidates.length];
            boolean skipped = false;
            for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
                invokeAwareMethods(filter);
                boolean[] match = filter.match(candidates, autoConfigurationMetadata);
                for (int i = 0; i < match.length; i++) {
                    if (!match[i]) {
                        skip[i] = true;
                        candidates[i] = null;
                        skipped = true;
                    }
                }
            }
            if (!skipped) {
                return configurations;
            }
            List<String> result = new ArrayList<>(candidates.length);
            for (int i = 0; i < candidates.length; i++) {
                if (!skip[i]) {
                    result.add(candidates[i]);
                }
            }
            if (logger.isTraceEnabled()) {
                int numberFiltered = configurations.size() - result.size();
                logger.trace("Filtered " + numberFiltered + " auto configuration class in "
                        + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
                        + " ms");
            }
            return new ArrayList<>(result);
        }

首先会根据spring.factories(spring-boot-autoconfiguration-2.1.1RELRASE.jar)中的filter进行过滤,另外过滤是还会用到另一个文件中的内容spring-autoconfigure-metadata.properties

    # Auto Configuration Import Filters
    org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
    org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
    org.springframework.boot.autoconfigure.condition.OnClassCondition,\
    org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

这里的过滤主要是根据spring-autoconfigure-metadata.properties文件中的内容进行过滤。简单看下OnClassCondition过滤的过程

    @Override
        protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
                AutoConfigurationMetadata autoConfigurationMetadata) {
            // Split the work and perform half in a background thread. Using a single
            // additional thread seems to offer the best performance. More threads make
            // things worse
            int split = autoConfigurationClasses.length / 2;
            OutcomesResolver firstHalfResolver = createOutcomesResolver(
                    autoConfigurationClasses, 0, split, autoConfigurationMetadata);
            OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(
                    autoConfigurationClasses, split, autoConfigurationClasses.length,
                    autoConfigurationMetadata, getBeanClassLoader());
            ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
            ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
            ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
            System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
            System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
            return outcomes;
        }

这里将配置类一分为二用了多线程提高效率,看下怎么过滤的,就是判断classpath中有没有该类

    private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
                    int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
                ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
                for (int i = start; i < end; i++) {
                    String autoConfigurationClass = autoConfigurationClasses[i];
                    if (autoConfigurationClass != null) {
                        //从properties中获取对应的类信息
                        String candidates = autoConfigurationMetadata
                                .get(autoConfigurationClass, "ConditionalOnClass");
                        if (candidates != null) {
                            outcomes[i - start] = getOutcome(candidates);  
                            //去classpath中看看有没有,没有则match为false,并显示对应的信息
                        }
                    }
                }
                return outcomes;
            }
    private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
                if (ClassNameFilter.MISSING.matches(className, classLoader)) {
                    return ConditionOutcome.noMatch(ConditionMessage
                            .forCondition(ConditionalOnClass.class)
                            .didNotFind("required class").items(Style.QUOTE, className));
                }
                return null;
            }

    MISSING {

                @Override
                public boolean matches(String className, ClassLoader classLoader) {
                    return !isPresent(className, classLoader);
                }

            };

    public static boolean isPresent(String className, ClassLoader classLoader) {
                if (classLoader == null) {
                    classLoader = ClassUtils.getDefaultClassLoader();
                }
                try {
                    forName(className, classLoader);
                    return true;
                }
                catch (Throwable ex) {
                    return false;
                }
            }

            private static Class<?> forName(String className, ClassLoader classLoader)
                    throws ClassNotFoundException {
                if (classLoader != null) {
                    return classLoader.loadClass(className);
                }
                return Class.forName(className);
            }

其实最终就是基于Class.forName(className);进行判断的,这也是condition使用的底层支持。

值得注意的是本例中的DubboAutoConfiguration并没在其中,所以对应的outcomes为null,match返回的是ture,经过OnClassCondition、OnWebApplicationCondition及OnBeanCondition的过滤后最终配置类还剩10个配置类(包含DubboAutoConfiguration)条目。

至此,condition出现了,上面的importfilter分别对应注解@ConditionalOnBean、@ConditionalOnClass、@ConditionalOnWebApplication含义分别是仅仅在当前上下文中存在某个对象时,才会启用;某个class位于类路径上,才会启用,该注解的参数对应的类必须存在,否则不解析该注解修饰的配置类;在web应用下才会启用该配置类,看下dubbo的自动配置类

    @Configuration
    @ConditionalOnProperty(prefix = DUBBO_PREFIX, name = "enabled", matchIfMissing = true, havingValue = "true")
    @ConditionalOnClass(AbstractConfig.class)
    public class DubboAutoConfiguration {

还用到了@ConditionalOnProperty注解,配置属性dubbo.enabled的值存在或者缺失都会启用该配置,另外还必须存在AbstractConfig类。

回过头去继续看lambda表达式:

    grouping.getImports().forEach(entry -> {
                        ConfigurationClass configurationClass = this.configurationClasses.get(
                                entry.getMetadata());
                        try {
                            processImports(configurationClass, asSourceClass(configurationClass),
                                    asSourceClasses(entry.getImportClassName()), false);
                        }
                        catch (BeanDefinitionStoreException ex) {
                            throw ex;
                        }
                        catch (Throwable ex) {
                            throw new BeanDefinitionStoreException(
                                    "Failed to process import candidates for configuration class [" +
                                            configurationClass.getMetadata().getClassName() + "]", ex);
                        }
                    });

针对上述过滤后剩余的10个配置类条目进行处理,继续调用

processImports(configurationClass, asSourceClass(configurationClass), asSourceClasses(entry.getImportClassName()), false);

这次上述两个条件都不满足,走到else当成配置类继续解析

    else {
        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
        // process it as an @Configuration class
        this.importStack.registerImport(
            currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
        processConfigurationClass(candidate.asConfigClass(configClass));
    }

而其中的@EnableDubboConfig注解导入了@Import(DubboConfigConfigurationSelector.class)

    /**
         * Single Dubbo Config Configuration
         *
         * @see EnableDubboConfig
         * @see DubboConfigConfiguration.Single
         */
        @EnableDubboConfig
        protected static class SingleDubboConfigConfiguration {
        }

因此会继续处理DubboConfigConfigurationSelector,这里采用的是递归处理。所有都处理完后得到ConfigClasses,继续处理配置类中的Bean,也就是用@Bean注解的bean信息,将其注册到IOC容器中。

    // 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());
                }
                this.reader.loadBeanDefinitions(configClasses);
                alreadyParsed.addAll(configClasses);
    /**
         * Determine if an item should be skipped based on {@code @Conditional} annotations.
         * @param metadata the meta data
         * @param phase the phase of the call
         * @return if the item should be skipped
         */
        public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
            if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
                return false;
            }

            if (phase == null) {
                if (metadata instanceof AnnotationMetadata &&
                        ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
                    return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
                }
                return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
            }

            List<Condition> conditions = new ArrayList<>();
            for (String[] conditionClasses : getConditionClasses(metadata)) {
                for (String conditionClass : conditionClasses) {
                    Condition condition = getCondition(conditionClass, this.context.getClassLoader());
                    conditions.add(condition);
                }
            }

            AnnotationAwareOrderComparator.sort(conditions);

            for (Condition condition : conditions) {
                ConfigurationPhase requiredPhase = null;
                if (condition instanceof ConfigurationCondition) {
                    requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
                }
                if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
                    return true;
                }
            }

            return false;
        }

至此配置类就解析完毕了,总结下:

1、首先对springboot配置类(com.jtt.hhl.DubboServerApplication)利用配置后置处理器ConfigurationClassPostProcessor进行解析,解析过程中利用其上的ComponentScan注解对其他组件进行扫描注册,包含(自定义配置类com.jtt.hhl.config.ConfigTest,由于@Component是@Configuration的元注解);利用其上的@EnableAutoConfiguration注解进行自动配置发现,主要是利用@Import导入AutoConfigurationImportSelector注解(解析时根据@Import注解导入的类型不同处理不同),读取META-INF/spring.factories中的配置类;还会对配置类上的@PropertySource注解、@ImportResource注解以及配置类内的@Bean注解进行处理。

2、自动配置的启用:根据spring.factories中的filter和spring-autoconfigure-metadata.properties中对应filter信息进行过滤,实际上就是自动配置的过程,如果classpath中有相应的配置就启用,继续进行配置类的解析操作,循环往复,直到所有启用的配置类都解析完毕,这个过程和使用xml配置时解析的过程类似。

3、@Conditional注解的处理,在配置类解析时会首先对配置类进行ConditionEvaluator处理(其实每个配置类都会进行处理,而且在loadBeanDefinitions(configClasses)配置类中的bean definition是也会进行@Conditional的处理(如果有的话)),判断条件是否满足,如果满足再继续进行配置类的解析(上面说com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration.OnClassCondition没在文件中,虽然当时没有被过滤,这里仍然会判断其上的注解条件),并将其中满足条件的bean注册到IOC容器中。因此在配置类com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration上的@Conditional注解在配置类解析时会进行处理。

至此,自动配置的全过程也就完毕了,自动配置的过程也就是SpringBoot启动类(也是配置类)的配置解析过程,配置类解析过程中会进行自动配置,自动配置就是读取META-INF/spring.factories中的配置类,然后利用@Conditional相关的注解,在classpath中判断对应的class是否存在(@ConditionalOnClass)、在propeties文件中判断对应的属性是否存在(@ConditionalOnProperty)、在IOC容器判断对应的bean是否存在(@ConditionalOnBean)而是否启用对应的配置,也是利用条件判断当自动配置不满足条件时可以实现自定义配置。

最后,没有实例化非懒加载单实例bean会在finishBeanFactoryInitialization(beanFactory);中进行实例化。

四、代码地址

https://github.com/honghailiang/springboot-dubbo

五、微信公众号

20191102100684\_4.png


来源:http://ddrv.cn

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » 【Spring Boot实战】源码解析Spring Boot自动配置原理

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏