Springboot启动的自动装配过程

一、总结Springboot自动配置的过程

抓住三点:

1.分析main方法中SpringApplication.run方法,该方法会创建spring容器并刷新容器;

2.分析复合注解@SpringApplication,该注解下面的@EnableAutoConfiguration下的@import注解;

3.SPI加载机制,加载META-INF/spring.fatories配置文件,按需加载;

Springboot启动时,是依靠启动类的main方法来进行启动的,而main方法中执行的是SpringApplication.run() 方法,而 SpringApplication.run() 方法中会创建spring的容器,并且刷新容器。而在刷新容器的时候就会去解析启动类,然后就会去解析启动类上的@SpringBootApplication 注解,而这个注解是个复合注解,这个注解中有一个@EnableAutoConfiguration 注解,这个注解就是开启自动配置,这个注解中又有 @Import注解引入了一个 AutoConfigurationImportSelector 这个类,这个类会进过一些核心方法,然后去扫描我们所有jar包下的 META-INF 下的 spring.factories 文件,而从这个配置文件中取找key为 EnableAutoConfiguration 类的全路径的值下面的所有配置都加载,这些配置里面都是有条件注解的,然后这些条件注解会根据你当前的项目依赖的jar包以及是否配置了符合这些条件注解的配置来进行装载的。

换成大白话,再精炼一下:

SpringBoot在启动的时候会调用run()方法,run()方法会刷新容器,刷新容器的时候,会扫描classpath下面的的包中META-INF/spring.factories文件,在这个文件中记录了好多的自动配置类,在刷新容器的时候会将这些自动配置类加载到容器中,然后在根据这些配置类中的条件注解,来判断是否将这些配置类在容器中进行实例化,这些条件主要是判断项目是否有相关jar包或是否引入了相关的bean。这样springboot就帮助我们完成了自动装配。

二、 源码分析

1. 分析SpringApplication.run方法:

        StopWatch stopWatch = new StopWatch();
        # 记录spring启动的时间标识
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        # 设置系统属性Headless的值
        this.configureHeadlessProperty();
        # 创建启动监听器
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            # 配置容器参数,将启动容器时的命令进行封装
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            # 加载外部配置到环境容器变量(包括配置要使用的PropertySource以及profile,并通知监听器容器要的环境变量配置好了)
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            # 跳过BeanInfo类的搜索(通常用于首先没有为应用程序中的Bean定于定义此类的情况)
            this.configureIgnoreBeanInfo(environment);
            # 判断环境变量中是否有banner的标识,有则打印
            Banner printedBanner = this.printBanner(environment);
            # 1 创建Application容器
            context = this.createApplicationContext();
            # 统一处理的异常信息
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            # 创建容器的一些操作
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            # 刷新Application容器
            this.refreshContext(context);  
            this.afterRefresh(context, applicationArguments);
            # 容器创建结束
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }
复制代码

SpringApplication.run() 方法中

  1. 第一个就是创建ApplicationContext容器。
  2. 第二个是刷新ApplicationContext容器。

在创建ApplicationContext时,会根据用户是否明确设置了 ApplicationContextClass 类型以及初始化阶段的推断结果,决定为当前SpringBoot应用创建什么类型的ApplicationContext。

protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch(this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
                }
            } catch (ClassNotFoundException var3) {
                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
            }
        }

        return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
    }
复制代码

创建完成ApplicationContext容器后,我们接着回到 SpringApplication.run() 方法中。

下面开始初始化各种插件在异常失败后给出的提示。
然后执行准备刷新上下文的一些操作。其实 prepareContext() 方法也是非常关键的,它起到了一个承上启下的作用。下面我们来看一下 prepareContext() 方法里面具体执行了什么。

  private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        this.postProcessApplicationContext(context);
        this.applyInitializers(context);
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            this.logStartupInfo(context.getParent() == null);
            this.logStartupProfileInfo(context);
        }

        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }

        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }

        if (this.lazyInitialization) {
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }
         # 关键:这里加载用配置,注意source加载出来就是XXXApplication这个启动类,从这里开始使用注解了
        Set<Object> sources = this.getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        # 加载启动类
        this.load(context, sources.toArray(new Object[0]));
        # 所有监听器开始加载如ApplicationContext容器中
        listeners.contextLoaded(context);
    }

复制代码

关键的地方主要就是 getAllSoures() 方法,这个方法中,获取到的一个source就是启动类XXXApplication。
这样就通过获取这个启动类就可以在后load()方法中取加载这个启动类到容器中。

然后,后面再通过 listeners.contextLoaded(context) ;

将所有监听器加载到ApplicationContext容器中。

最后就是我们上面说的核心的第二部刷新ApplicationContext容器操作,如果没有这一步操作上面的内容也都白做的,通过 SpringApplication的refreshContext(context) 方法完成最后一道工序将启动类上的注解配置,刷新到当前运行的容器环境中。

2. 启动类上的注解

上面我们说到在SpringApplication的 run() 方法中,通过调用自己的 prepareContext() 方法,在 prepareContext() 方法中又调用 getAllSources() 方法,然后去获取启动类,然后通过SpringApplication的 load() 方法,去加载启动类,然后在刷新容器的时候就会去将启动类在容器中进行实例化。

在刷新ApplicationContext容器时,就开始解析启动类上的注解了。

启动类 DemoApplication 就只有一个注解 @SpringBootApplication ,那么下面来看一下这个注解:

图片.png

可以看到这个注解是一个复合注解,有三个关键注解需要说明一下。

@SpringBootConfiguration

@SpringBootConfiguration 这个注解说明再点进去查看详情发现就是一个@Configuration 注解,这说明启动类就是一个配置类。支持Spring以JavaConfig的形式启动。

@ComponentScan

这个注解,从字面的意思上也能看出来,就是组件扫描的意思,即默认扫描当前package以及其子包下面的spring的注解,例如: @Controller 、 @Service 、 @Component 等等注解。

@EnableAutoConfiguration

@EnableAutoConfiguration 这个注解也是一个复合注解:

图片.png

这个注解是比较核心的一个注解,springboot的主要自动配置原理基本上都来自@EnableAutoConfiguration这个注解的配置,那么我们通过看这个注解的源码可以发现有两个注解比较重要的。

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)

复制代码

@AutoConfigurationPackage 这个注解字面的意思是 自动配置包 ,那么我们点进去看看里面是什么样的。

图片.png

还是一个复合注解,但是最终依赖的确实 @Import 这个注解,这个注解后面我们会介绍,现在先明白它就是给Spring容器引入组件的功能的一个注解。

那么我们接着来看看 AutoConfigurationPackages.Registrar.class 这个类里面的代码。

图片.png

图片.png

这两张图就是这个 AutoConfigurationPackages.Registrar 这个类的关键部分,说实话,我是没看出来什么东西。但是网上搜到的是这个register()方法的作用是,用来自动注册一些组件中的配置,例如JPA的 @Entity 这个注解,这里就是会开启自动扫描这类注解的功能。

** @Import(AutoConfigurationImportSelector.class)**

我们接着回来看 @EnableAutoConfiguration 下的@Import(AutoConfigurationImportSelector.class) 这个注解的功能。进入到AutoConfigurationImportSelector 这个类里面后源码如下:

 public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            # getAutoConfigurationEntry 此方法很关键
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }
复制代码

然后我们进入 getAutoConfigurationEntry() 方法来看看:

 protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
         # getCandidateConfigurations 很关键
         List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }
复制代码

我们继续进入 getCandidateConfigurations() 方法:

  protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }
复制代码

看来最核心的方法是 SpringFactroiesLoader.loadFactoryNames() 方法了,我们再进入看看:

 public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }
复制代码

包的好深,居然还有一层,那么继续进入 loadSpringFactories() 方法。

 private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }
复制代码

终于到最后一层了,算是“拨开云雾见天日,守得云开见月明”,下面就来梳理一下loadSpringFactories()方法。

首先 FACTORIES_RESOURCE_LOCATION 这个常量的值是:

"META-INF/spring.factories"
/**
 * 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";
复制代码

所以第一个端核心代码的意思是:

启动的时候会扫描所有jar包下 META-INF/spring.factories 这个文件。第二段代码的意思是将这些扫描到的文件转成Properties对象,后面两个核心代码的意思就是说将加载到的Properties对象放入到缓存中。

然后 getCandidateConfigurations() 方法,是只获取了key是EnableAutoConfiguration.class 的配置。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }
复制代码

我们看到 getCandidateConfigurations() 方法,通过SpringFactoriesLoader.loadFactoryNames() 获取到了118个配置(待确认)。

图片.png

那么我们以第一个配置类:

org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration 来看一下,这些类都是如果实现的。

打开org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration 的源码:

图片.png

我们看到这个类有三个注解 @Configuration 、 @AutoConfigureAfter 、@ConditionalOnProperty 、因为有 @Configuration 注解所以它也是一个配置类,然后第二注解中的参数类 JmxAutoConfiguration.class 进入之后是这样的:
也是存在 @ConditionalOnProperty 注解的。那看来关键点就是 @ConditionalOnProperty这个注解了。

这个注解其实是一个条件判断注解,这个条件注解后面的参数的意思是当存在系统属性前缀为spring.application.admin ,并且属性名称为 enabled ,并且值为 true 时,才加载当前这个Bean并进行实例化。
这种spring4.0后面出现的的条件注解,可以极大的增加了框架的灵活性和扩展性,可以保证很多组件可以通过后期配置,而且阅读源码的人,通过这些注解就能明白在什么情况下才会实例化当前Bean。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享