您的位置:首页 > 编程语言 > Java开发

Spring Boot 自动装配(二) - @SpringBootApplication、@AutoConfigurationPackage、@EnableAutoConfiguration

2019-12-02 22:13 881 查看

目录

  • 2.2、自动装配的组件内部实现
  • 3、总结
  • 目录

    前言

            最近在学习Spring Boot相关的课程,过程中以笔记的形式记录下来,方便以后回忆,同时也在这里和大家探讨探讨,文章中有漏的或者有补充的、错误的都希望大家能够及时提出来,本人在此先谢谢了!

    开始之前呢,希望大家带着几个问题去学习:
    1、Spring Boot 自动装配是什么?
    2、这个功能在什么时代背景下发明产生的?
    3、这个功能有什么用?
    4、怎么实现的?
    5、优点和缺点是什么?
    6、这个功能能应用在工作中?
    这是对自我的提问,我认为带着问题去学习,是一种更好的学习方式,有利于加深理解。好了,接下来进入主题。

    1、起源

            在上篇文章中我们讲到 Spring 注解虽然可以代替以往XML的形式,帮助我们自动注册Bean以及初始化组件,简化我们的开发,但还是做不到真正意义上的自动装配,今天我们就来讲讲 Spring Boot 是如何深度整合 Spring 注解编程模型、@Enable 模块驱动及条件装配等 Spring 原生特性来实现自动装配的。

    注:本篇文章所用到的 Spring Boot版本是 2.1.6.BUILD-SNAPSHOT

    2、Spring Boot 自动装配实现

            我们都知道 Spring Boot 的启动过程非常简单,只需要启动一个 main 方法,项目就可以运行,就算依赖了诸多外部模块如:MVC、Redis等,也不需要我们进行过多的配置,那它的底层原理是什么呢?接下来,我们就一起去看一看。

    我们先来看一段 Spring Boot 的启动类代码:

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

    我们需要关注的是

    @SpringBootApplication
    这个注解:

    @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 {
    
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};
    
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};
    
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};
    
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};
    
    }

    我们来看一看它的组成部分:

    • @SpringBootConfiguration
      :它里面标注了
      @Configuration
      注解,上篇文章说过,表明这是个配置类,功能与
      @Configuration
      无异。
    • @EnableAutoConfiguration
      :这个就是实现自动装配的核心注解,是用来激活自动装配的,其中默认路径扫描以及组件装配、排除等都通过它来实现。
    • @ComponentScan
      :上篇文章我们讲过这是用来扫描被
      @Component
      标注的类 ,只不过这里是用来过滤 Bean 的,指定哪些类不进行扫描,而且用的是自定义规则。
    • Class<?>[] exclude()
      :根据class来排除,排除指定的类加入spring容器,传入的类型是class类型。且继承自
      @EnableAutoConfiguration
      中的属性。
    • String[] excludeName()
      :根据class name来排除,排除特定的类加入spring容器,参数类型是class的全类名字符串数组。同样继承自
      @EnableAutoConfiguration
    • String[] scanBasePackages()
      :可以指定多个包名进行扫描。继承自
      @ComponentScan
    • Class<?>[] scanBasePackageClasses()
      :可以指定多个类或接口的class,然后扫描 class 所在包下的所有组件。同样继承自
      @ComponentScan

    2.1、@EnableAutoConfiguration 实现

            上面我们说到

    @EnableAutoConfiguration
    是实现自动装配的核心注解,是用来激活自动装配的,看注解前缀我们应该知道是上篇文章中所讲的 Spring @Enable 模块驱动的设计模式,所以它必然会有
    @Import
    导入的被
    @Configuration
    标注的类或实现
    ImportSelector
    ImportBeanDefinitionRegistrar
    接口的类。接着,我们来看看它的定义:

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

    可以看到它由两部分组成:

    • @AutoConfigurationPackage
      :这是用来将启动类所在包,以及下面所有子包里面的所有组件扫描到Spring容器中,这里的组件是指被
      @Component
      或其派生注解标注的类。这也就是为什么不用标注
      @ComponentScan
      的原因。
    • @Import(AutoConfigurationImportSelector.class)
      :这里导入的是实现了
      ImportSelector
      接口的类,组件自动装配的逻辑均在重写的
      selectImports
      方法中实现。

    接下来我们就来看看这两者具体是怎么实现的。

    2.1.1、 获取默认包扫描路径

    我们先来看看

    Spring Boot
    是如何通过
    @AutoConfigurationPackage
    注解获取默认包扫描路径的,进入它的实现:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
    }

    可以看到它是通过

    @Import
    导入了
    AutoConfigurationPackages.Registrar
    类,该类实现了
    ImportBeanDefinitionRegistrar
    接口,所以按照上篇文章所讲的,它是在重写的方法中直接注册相关组件。继续往下:

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    register(registry, new PackageImport(metadata).getPackageName());
    }
    
    ....
    }
    private static final class PackageImport {
    
    private final String packageName;
    
    PackageImport(AnnotationMetadata metadata) {
    this.packageName = ClassUtils.getPackageName(metadata.getClassName());
    }
    ....
    }

    这里主要是通过

    metadata
    元数据信息构造
    PackageImport
    类。先获取启动类的类名,再通过
    ClassUtils.getPackageName
    获取启动类所在的包名。我们接着往下看:

    public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    if (registry.containsBeanDefinition(BEAN)) {
    BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
    ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
    constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
    }
    else {
    GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
    beanDefinition.setBeanClass(BasePackages.class);
    beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    registry.registerBeanDefinition(BEAN, beanDefinition);
    }
    }

    最后就是将这个包名保存至

    BasePackages
    类中,然后通过
    BeanDefinitionRegistry
    将其注册,进行后续处理,至此该流程结束。

    2.1.2、获取自动装配的组件

    该部分就是实现自动装配的入口,从上面得知这里也是通过

    @Import
    来实现的,来看看导入的类:

    public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
    ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    
    ....
    
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
    return NO_IMPORTS;
    }
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
    .loadMetadata(this.beanClassLoader);
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
    annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    
    ....
    }

    主要关注重写的

    selectImports
    方法,其中
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
    是加载自动装配的元信息。而
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,                 annotationMetadata)
    该方法返回的就是自动装配的组件,我们进去看看:

    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
    AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
    return EMPTY_ENTRY;
    }
    
    // 获取 @EnableAutoConfigoration 标注类的元信息,也就是获取该注解 exclude 和 excludeName 属性值
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    
    // 该方法就是获取自动装配的类名集合
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    
    // 去除重复的自动装配组件,就是将List转为Set进行去重
    configurations = removeDuplicates(configurations);
    
    // 这部分就是根据上面获取的 exclude 及 excludeName 属性值,排除指定的类
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    
    // 这里是过滤那些依赖不满足的自动装配 Class
    configurations = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    
    // 返回的就是经过一系列去重、排除、过滤等操作后的自动装配组件
    return new AutoConfigurationEntry(configurations, exclusions);
    }

    该方法中就是先获取待自动装配组件的类名集合,然后通过一些列的去重、排除、过滤,最终返回自动装配的类名集合。主要关注

    getCandidateConfigurations(annotationMetadata, attributes)
    这个方法,里面是如何获取自动装配的类名集合:

    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;
    }

    其中

    getSpringFactoriesLoaderFactoryClass()
    返回的是
    EnableAutoConfiguration.class

    继续往下,执行的是
    SpringFactoriesLoader#loadFactoryNames
    方法:

    public final class SpringFactoriesLoader {
    
    ...
    
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    
    // 前面可以看到,这里的 factoryClass 是 EnableAutoConfiguration.class
    String factoryClassName = factoryClass.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }
    
    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);
    }
    }
    ...
    }

    最终的实现逻辑都在这里,主要过程如下:

    (1)搜索classpath路径下以及所有外部jar包下的META-INF文件夹中的

    spring.factories
    文件。主要是
    spring-boot-autoconfigur
    包下的

    # Initializers
    org.springframework.context.ApplicationContextInitializer=\
    org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
    org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
    
    # Application Listeners
    org.springframework.context.ApplicationListener=\
    org.springframework.boot.autoconfigure.BackgroundPreinitializer
    
    # Auto Configuration Import Listeners
    org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
    org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
    
    # 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
    
    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
    
    ...

    可以看到其中内容,存储的是key-value格式的数据,且key是一个类的全路径名称,value是多个类的全路径名称,且以逗号分割。

    (2)将所有的

    spring.factories
    文件转成
    Properties
    格式,将里面key-value格式的数据转成Map,该Map的value是一个List,之后将相同Key的value合并到List中,将该Map作为方法返回值返回。

    (3)返回到

    loadFactoryNames
    方法,通过上面得知
    factoryClassName
    的值为
    EnableAutoConfiguration
    ,所以通过
    getOrDefault(factoryClassName, Collections.emptyList())
    方法,获取 key 为
    EnableAutoConfiguration
    的类名集合。

    ps:

    getOrDefault
    第一个入参是key的name,如果key不存在,则直接返回第二个参数值

    至此,流程结束,最后返回的就是自动装配的组件,其中有我们比较熟悉的Redis、JDBC、SpringMVC等,可以看到一个特点,这些自动装配的组件都是以

    AutoConfiguration
    结尾。但该组件列表只是候选组件,因为后面还有去重、排除、过滤等一系列操作,这里就不再详细述说。下面我们来看看自动装配的组件内部是怎么样的。

    2.2、自动装配的组件内部实现

    就拿比较熟悉的 Web MVC 来看,看看是如何实现 Web MVC 自动装配的。先来代码组成部分:

    @Configuration
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
    @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
    @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
    @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
    ValidationAutoConfiguration.class })
    public class WebMvcAutoConfiguration {
    ...
    @Configuration
    @Import(EnableWebMvcConfiguration.class)
    @EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class})
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ResourceLoaderAware {
    ...
    @Bean
    @ConditionalOnBean(View.class)
    @ConditionalOnMissingBean
    public BeanNameViewResolver beanNameViewResolver() {
    ...
    }
    ...
    }
    
    @Configuration
    public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
    
    @Bean
    @Override
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
    ...
    }
    
    @Bean
    @Primary
    @Override
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
    ...
    }
    }
    ...
    }
    • 注解部分:
      @Configuration
      :这个大家都比较熟悉,标识该类是一个配置类
    • @ConditionalXXX
      :这是上篇文章所讲的 Spring 条件装配,只不过经由 Spring Boot 扩展形成了自己的条件化自动装配,且都是
      @Conditional
      的派生注解。
      @ConditionalOnWebApplication
      :参数值是 Type 类型的枚举,当前项目类型是任意、Web、Reactive其中之一则实例化该 Bean。这里指定如果为 Web 项目才满足条件。
    • @ConditionalOnClass
      :参数是 Class 数组,当给定的类名在类路径上存在,则实例化当前Bean。这里当
      Servlet.class
      DispatcherServlet.class
      WebMvcConfigurer.class
      存在才满足条件。
    • @ConditionalOnMissingBean
      :参数是也是 Class 数组,当给定的类没有实例化时,则实例化当前Bean。这里指定当
      WebMvcConfigurationSupport
      该类没有实例化时,才满足条件。
  • 装配顺序
      @AutoConfigureOrder
      :参数是int类型的数值,数越小越先初始化。
    • @AutoConfigureAfter
      :参数是 Class 数组,在指定的配置类初始化后再加载。
    • @AutoConfigureBefore
      :参数同样是 Class 数组,在指定的配置类初始化前加载。
  • 代码部分:
      这部分就比较直接了,实例化了和 Web MVC 相关的Bean,如
      HandlerAdapter
      HandlerMapping
      ViewResolver
      等。其中,出现了
      DelegatingWebMvcConfiguration
      类,这是上篇文章所讲的
      @EnableWebMvc
      @Import
      导入的配置类。

    可以看到,在

    Spring Boot
    自动装配的类中,经过了一系列的
    @Conditional
    条件判断,然后实例化某个模块需要的Bean,且无需我们配置任何东西,当然,这都是默认实现,当这些不满足我们的要求时,我们还得手动操作。

    3、总结

            关于Spring boot自动装配的内容就告一段落,不难看出Spring Boot自动装配所依赖的注解驱动、

    @Enable
    模块驱动、条件装配等特性均来自 Spring Framework。而自动装配的配置类均来源于
    spring.factories
    文件中。核心则是基于“约定大于配置”理念,通俗的说,就是Spring boot为我们提供了一套默认的配置,只有当默认的配置不满足我们的需求时,我们再去修改默认配置。当然它也存在缺点就是组件的高度集成,使用的时候很难知道底层实现,加深了理解难度。

    以上就是本章的内容,如过文章中有错误或者需要补充的请及时提出,本人感激不尽。



    参考:

    《Spring Boot 编程思想》

  • 内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: