关于springboot:深入学习-Spring-Web-开发-应用启动

2次阅读

共计 12864 个字符,预计需要花费 33 分钟才能阅读完成。

不晓得读者在编写我的项目的时候,有没有思考过启动类中为何须要同时应用 @SpringBootApplication 和 SpringApplication,它们的作用别离又是什么?上面让咱们一起来一探到底。

@SpringBootApplication

@SpringBootApplication 引入了 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 三个注解:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {// ...}

其中 @SpringBootConfiguration 又引入了 @Configuration,从而让 HelloWorldApplication 成为了一个配置类。

@Configuration
@Indexed
public @interface SpringBootConfiguration {// ...}

@EnableAutoConfiguration 通过 @Import 引入了 AutoConfigurationImportSelector 类,又通过 @AutoConfigurationPackage 间接地引入了 AutoConfigurationPackages.Registrar 类。

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {// ...}
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {// ...}

总的来说,就是 @SpringBootApplication 通过 @Configuration 注解让启动类 HelloWorldApplication 成为了配置类,通过 @EnableAutoConfiguration 开启了主动配置的扫描,通过 @ComponentScan 开启了 Spring Bean 的主动扫描。

SpringApplication

SpringApplication.run() 是整个 Spring 利用的入口。围绕上述提到的注解和类,能够梳理出,其外围的启动流程如下:

SpringFactoriesLoader

SpringApplication 的动态 run() 办法,会先创立一个 SpringApplication 实例,再执行它的实例 run() 办法。创立 SpringApplication 的时候,会调用 SpringFactoriesLoader 的 loadSpringFactories() 办法,最终会触发对所有类门路的 jar 包中的 META-INF/spring.factories 文件的加载。如在我的 HelloWorld 我的项目中,会加载如下几个文件:

jar:file:/Users/susamlu/.m2/repository/org/springframework/boot/spring-boot/2.7.2/spring-boot-2.7.2.jar!/META-INF/spring.factories
jar:file:/Users/susamlu/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.7.2/spring-boot-autoconfigure-2.7.2.jar!/META-INF/spring.factories
jar:file:/Users/susamlu/.m2/repository/org/springframework/spring-beans/5.3.22/spring-beans-5.3.22.jar!/META-INF/spring.factories

上述 META-INF/spring.factories 文件蕴含的具体内容,在此不作赘述。要理解这些文件的作用,能够通过查看 SpringFactoriesLoader 类的代码和正文进行理解。

SpringFactoriesLoader 类的次要作用是提供 Spring 框架外部一种加载工厂的形式,它能够从多个类门路的 jar 包中的 META-INF/spring.factories 文件中加载并实例化给定的工厂。其中,spring.factories 的配置须要遵循 properties 配置的格局,并且个别以接口的全限定类名为 key,以具体实现类的全限定类名为 value。例如:

example.MyService=example.MyServiceImpl1,example.MyServiceImpl2

SpringFactoriesLoader 提供了 loadFactories()、loadFactoryNames() 两个公共静态方法,一个用于获取工厂实例,一个用于获取工厂类的全限定类名。因而,META-INF/spring.factories 实际上是将一些工厂类(或者是接口的具体实现类)当时配置在文件中,以供给用在须要时获取,通过这种形式,能够绕开获取实例必须从 Spring IoC 容器获取的限度(在全副 Spring Bean 加载实现之前,通过这种形式获取实例是很有必要的)。

EnableAutoConfiguration

META-INF/spring.factories 还有另一个妙用,就是在此文件中通过定义 EnableAutoConfiguration,让咱们自定义的 jar 包中的类也能够成为 Spring Boot 的主动配置类,比方咱们定义了如下配置类:

package example;

public class MyAutoConfiguration {
    
    @Bean
    public MyBean() {return new MyBean();
    }
    
}

那么,只有在 resources 目录的 META-INF/spring.factories 文件中定义如下配置:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  example.MyAutoConfiguration

接着,将该我的项目打包成 jar 包,在咱们将该 jar 包引入到某个我的项目时,MyAutoConfiguration 类就会被当成主动配置类而被 Spring Boot 主动加载。它的具体的工作原理,将在本文前面的大节中进行解析。

prepareContext

SpringApplication 的实例 run() 办法会调用两个十分重要的办法:prepareContext() 和 refreshContext(),这两个办法通过它们的名字就能够大略猜出其作用了,实际上,一个是用来筹备在利用启动前须要事后筹备的内容的,一个是用来执行在利用启动时须要执行的外围办法的。

通过代码的追踪,咱们会发现 prepareContext() 做了一个比拟要害的操作,就是执行了本身实例的 load() 办法,该办法会将 HelloWorldApplication 注册到 Spring IoC 容器中,实现了这一步,前面利用启动时进行主动扫描、主动配置等,就能够找到基类了。

refreshContext

refreshContext() 最终会调用 AbstractApplicationContext 的 refresh() 办法,refresh() 通过一系列简单的调用之后,会将后面被注册到 Spring IoC 容器中的 HelloWorldApplication 取出来,并从 HelloWorldApplication 开始,进行配置类的解析。

doProcessConfigurationClass

配置解析经由 ConfigurationClassPostProcessor 类的 processConfigBeanDefinitions() 办法,调起配置解析的入口:ConfigurationClassParser 类的 parse() 办法,parse() 辗转之后,最终会调用到本身实例的 doProcessConfigurationClass() 办法,doProcessConfigurationClass() 是 Spring Bean 加载阶段当之无愧的外围办法。

class ConfigurationClassParser {
    
    // ...

    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(解析 @PropertySources 注解)for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class,
                org.springframework.context.annotation.PropertySource.class)) {// ...}

        // Process any @ComponentScan annotations(解析 @ComponentScan 注解)Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
        if (!componentScans.isEmpty() &&
                !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {// ...}

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

        // Process any @ImportResource annotations(解析 @ImportResource 注解)AnnotationAttributes importResource =
                AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
        if (importResource != null) {// ...}

        // Process individual @Bean methods(解析带有 @Bean 注解的办法)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()) {// ...}

        // No superclass -> processing is complete(不存在父类,则解析结束)return null;
    }
    
    // ...
    
}

解析配置类的时候,会进行如下解决:

  • 解析外部类
  • 解析 @PropertySources 注解
  • 解析 @ComponentScan 注解
  • 解析 @Import 注解
  • 解析 @ImportResource 注解
  • 解析带有 @Bean 注解的办法
  • 解析父接口的默认办法
  • 解析父类

配置类的解析波及到的内容比拟多,这里只对 解析 @ComponentScan 注解 这一步进行剖析,其余局部前面会有一篇独自的文章进行解说。

ComponentScan

如果配置类带有 @ComponentScan 注解(毫无疑问,HelloWorldApplication 类是带有 @ComponentScan 注解的),就会触发主动扫描,调用 ComponentScanAnnotationParser 类的 parse() 办法。

parse() 办法会创立一个 ClassPathBeanDefinitionScanner 实例,并设置 scanner 的 includeFilters 和 excludeFilters:

class ComponentScanAnnotationParser {
    
    // ...

    public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
                componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

        // ...

        for (AnnotationAttributes includeFilterAttributes : componentScan.getAnnotationArray("includeFilters")) {
            List<TypeFilter> typeFilters = TypeFilterUtils.createTypeFiltersFor(includeFilterAttributes, this.environment,
                    this.resourceLoader, this.registry);
            for (TypeFilter typeFilter : typeFilters) {scanner.addIncludeFilter(typeFilter);
            }
        }
        for (AnnotationAttributes excludeFilterAttributes : componentScan.getAnnotationArray("excludeFilters")) {
            List<TypeFilter> typeFilters = TypeFilterUtils.createTypeFiltersFor(excludeFilterAttributes, this.environment,
                    this.resourceLoader, this.registry);
            for (TypeFilter typeFilter : typeFilters) {scanner.addExcludeFilter(typeFilter);
            }
        }

        // ...

        scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
            @Override
            protected boolean matchClassName(String className) {return declaringClass.equals(className);
            }
        });
        return scanner.doScan(StringUtils.toStringArray(basePackages));
    }
    
    // ...
    
}

filter 的设置策略取决于 @ComponentScan 注解,@ComponentScan 注解的 useDefaultFilters 属性默认为 true,因而默认会采纳 Spring 框架默认定义的 filter,最终失去两个 includeFilter,这两个 includeFilter 都是 AnnotationTypeFilter,其中一个的 annotationType 为 org.springframework.stereotype.Component,另一个的 annotationType 为 javax.annotation.ManagedBean。接着,会将 @ComponentScan 注解指定的 includeFilters 和 excludeFilters 增加到 scanner 中,由上文可知,@ComponentScan 设置了两个 excludeFilters:TypeExcludeFilter 和 AutoConfigurationExcludeFilter,即 TypeExcludeFilter 和 AutoConfigurationExcludeFilter 都会被退出到 scanner 的 excludeFilters 中。最初,再将自定义的 excludeFilter:匿名 AbstractTypeHierarchyTraversingFilter 增加到 scanner 的 excludeFilters 中。最终,scanner 会失去 2 个 includeFilter 和 3 个 excludeFilter。即:

  • includeFilters:

    • AnnotationTypeFilter(org.springframework.stereotype.Component)
    • AnnotationTypeFilter(javax.annotation.ManagedBean)
  • excludeFilters:

    • TypeExcludeFilter
    • AutoConfigurationExcludeFilter
    • AbstractTypeHierarchyTraversingFilter

其中,AnnotationTypeFilter 的作用是过滤蕴含有指定注解的类,两个 AnnotationTypeFilter 都是 includeFilter,因而,只有类蕴含 @Component 或 @ManagedBean 注解,就会被扫描到。TypeExcludeFilter 次要是预留于扩大之用,即通过 TypeExcludeFilter 能够自定义排除扫描的规定,AutoConfigurationExcludeFilter 次要用于排除对主动配置类的扫描,自定义的匿名 AbstractTypeHierarchyTraversingFilter 次要是用来排除扫描的基类(即 HelloWorldApplication),以防止扫描陷入死循环之中。因而,总的来说,默认状况下,@ComponentScan 注解的解析过程中,会将基类和主动配置类排除,将蕴含有 @Component 注解的类扫描并增加到 Spring IoC 容器中。

另外,parse() 办法会获取 @ComponentScan 注解指定的 basePackages,如果没有指定,则以以后类所在包的包门路作为 basePackage。接着,再通过 ClassPathScanningCandidateComponentProvider 的 scanCandidateComponents() 办法,扫描 basePackage 下的所有 class 文件,并将符合要求(通过 scanner 的 includeFilters 和 excludeFilters 进行筛选)的候选类增加到 Spring IoC 容器中。如果这个过程中,扫描到了配置类,则又从新回到下面解析配置类的步骤中,一直递归,直到将全副类加载实现。

AutoConfigurationImportSelector

主动扫描逻辑执行结束,调用又从新回到 ConfigurationClassParser 类的 parse() 办法,接着会触发 AutoConfigurationImportSelector 的外部类 AutoConfigurationGroup 的 process() 办法的执行。process() 办法会将全副主动配置类加载到内存中,并依照肯定规定进行筛选。其中,加载的外围代码如下:

public class AutoConfigurationImportSelector /* ... */ {
    
    // ...

    private static class AutoConfigurationGroup /* ... */ {protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
            List<String> configurations = new ArrayList<>(SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
            ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
            Assert.notEmpty(configurations,
                    "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you"
                            + "are using a custom packaging, make sure that file is correct.");
            return configurations;
        }

        // ...

        protected Class<?> getSpringFactoriesLoaderFactoryClass() {return EnableAutoConfiguration.class;}

        // ...

    }

    // ...
    
}

加载的内容蕴含两局部,一部分是从 META-INF/spring.factories 加载来的,一部分是从 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 加载来的。从 META-INF/spring.factories 加载的,正是 key 为 EnableAutoConfiguration 的配置类,这就是上述所讲的 EnableAutoConfiguration 为何起作用的起因。而文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 记录的是 Spring Boot 全副的主动配置类。加载阶段获取到的配置类,还须要通过肯定规定的筛选,最终筛选进去的配置类,才会被真正解析。

META-INF/spring.factories 中定义的配置类到此时才进行收集、解析,为何该文件早早就被加载了?其实,当 loadFactories()、loadFactoryNames() 两个办法中的一个被调用时,loadSpringFactories() 办法接着就会执行,loadSpringFactories() 办法会加载 META-INF/spring.factories 文件,且只会加载一次,加载失去的内容会被缓存起来。在利用启动之始,loadFactoryNames() 就会被执行,以初始化一些默认的 Bean。因而,在这个时候,META-INF/spring.factories 文件就被加载了,且前面也无需再次加载了。

spring-boot-2.7.2.jarMETA-INF/spring.factories 文件中定义了如下几个 filter,主动配置类正是依据这几个 filter 进行过滤的。

# 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

这三个类别离对应注解:@ConditionalOnBean、@ConditionalOnClass、@ConditionalOnWebApplication。其中,@ConditionalOnBean 示意当 Spring 容器中存在某个 Bean 时,配置类才会失效,它通常会跟 @ConditionalOnSingleCandidate 注解一起起作用,@ConditionalOnSingleCandidate 的作用是示意容器中的 Bean 为单例时,配置类才起作用。@ConditionalOnClass 示意配置类在利用中存在某个指定的类时才失效。@ConditionalOnWebApplication 示意我的项目是 web 我的项目时,配置类才失效,它有一个 type 参数,能够指定当我的项目是基于 servlet 的我的项目(type=SERVLET)或者是基于 reactive 的我的项目(type=REACTIVE)时才失效,默认为只有是其中一种我的项目就失效。

通过层层筛选后,最终失去的主动配置类,就会被当做配置类通过 doProcessConfigurationClass() 办法一一进行解析。

到了这里,咱们对 Spring Boot 我的项目是如何启动的,就有了一个根本的意识,咱们回顾下前文提到的几个注解:@Configuration、@EnableAutoConfiguration、@ComponentScan,以及 import 进来的类:AutoConfigurationImportSelector、AutoConfigurationPackages.Registrar,除了 AutoConfigurationPackages.Registrar,上文中都曾经有相干的解析了,而 AutoConfigurationPackages.Registrar 又是用来做什么的呢?在理解完相干的代码之后,能够从这个类的正文中失去答案:它次要是用于记录主动配置类的包门路的,以便于前面有须要的时候应用,如 JPA 的实体扫描等。

主动配置类解析实现之后,还有一个问题尚未谈及,即主动配置类本身是在什么时候被加载到 Spring IoC 容器中的。其实,当主动扫描和主动配置的逻辑执行完之后,办法调用又从新回到了 ConfigurationClassPostProcessor 类的 processConfigBeanDefinitions() 办法,该办法会将主动配置类对立加载到 Spring IoC 容器中,至此,所有的 Bean 就加载实现了。

小结

聊了那么多之后,让咱们回到文章开始提到的问题:启动类中为何须要同时应用 @SpringBootApplication 和 SpringApplication,它们的作用别离又是什么?

显然,SpringApplication.run() 作为整个 Spring 利用的入口,是必须要执行的,否则接下来的一系列操作都无从执行。因而,SpringApplication 是必不可少的,而 @SpringBootApplication 呢?为了验证 @SpringBootApplication 是否也是必须的,咱们能够做一些试验。咱们能够将启动类的 @SpringBootApplication 注解去掉,而后启动利用,看看这是否是可行的。事实上,利用会启动失败,并且咱们会失去上面这个谬误提醒:

Web application could not be started as there was no org.springframework.boot.web.servlet.server.ServletWebServerFactory bean defined in the context.

这其实是因为主动配置没有加载而导致的,于是咱们给启动类加上 @EnableAutoConfiguration 注解,再启动利用,会发现利用能够启动了。(为什么会这样?其实是因为 @EnableAutoConfiguration 对 AutoConfigurationImportSelector 的引入导致的,如果利用没有引入 AutoConfigurationImportSelector 类,就不会触发在其中定义的一系列的主动配置解析逻辑。)然而这个时候,咱们会发现接口拜访不了了,于是接着给启动类加上 @ComponentScan 注解,再次启动利用,接口就能够失常拜访了。这时候仿佛所有都失常了:利用能够失常启动,接口能够失常拜访。咱们会发现,没有 @Configuration 注解仿佛也是可行的。有没有 @Configuration 的区别是什么呢?这里咱们先不答复,等咱们理解完 Spring 配置类的个性后,置信这个问题也会迎刃而解。

最初,咱们再总结一下:@SpringBootApplication 和 SpringApplication 在启动类中都是必须的,@SpringBootApplication 标识了利用须要进行主动配置类和 Spring Bean 的主动加载,SpringApplication.run() 作为利用的最后入口,它们在利用的启动过程中,互相起作用,缺一不可。

正文完
 0