关于java:SpringBoot自动装配

36次阅读

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

SpringBoot 是对 Spring 的一种扩大,其中比拟重要的扩大性能就是主动拆卸:通过注解对罕用的配置做默认配置,简化 xml 配置内容。本文会对 Spring 的主动配置的原理和局部源码进行解析,本文次要参考了 Spring 的官网文档。

主动拆卸的组件

SpringBoot 主动拆卸通过多局部组件协调实现,这些组件次要有上面几种,这几种组件之间协调工作,最终实现了 SpringBoot 的主动拆卸。

  1. @EnableAutoConfiguration:用于依据用户所援用的 jar 包主动拆卸 Spring 容器,比方用户在 ClassPath 中蕴含了 HSQLDB,然而没有手动配置数据库连贯,那么 Spring 会主动应用 HSQLDB 作为数据源。
  2. @Condition:不同状况下依照条件进行拆卸,Spring 的 JdbcTemplate 是不是在 Classpath 外面?如果是,并且 DataSource 也存在,就主动配置一个 JdbcTemplate 的 Bean
  3. @ComponentScan:扫描指定包上面的 @Component 注解的组件。

@EnableAutoConfiguration 注解

Spring 的主动拆卸倒退大抵能够分为三个阶段:

  1. 全手工配置的 XML 文件阶段,用户须要的 Bean 全副须要在 XML 文件中申明,用户手工治理全副的 Bean。
  2. 半手工配置的注解阶段,用户能够装置需要 Enable 对应的功能模块,如增加 @EnableWebMvc 能够启用 MVC 性能。
  3. 全自动配置的 SpringBoot,用户只须要引入对应的 starter 包,Spring 会通过 factories 机制主动拆卸须要的模块。

全手工配置的 XML 文件示意图:

半自动注解配置示意图:

全自动注解配置示意图:

Spring 启用全自动配置性能的注解就是 @EnableAutoConfiguration,利用增加了 @EnableAutoConfiguration 注解之后,会读取所有 jar 包上面的 spring.factories 文件,获取文件中配置的主动拆卸模块,而后去拆卸对应的模块。

@EnableAutoConfiguration 的性能可总结为:使 Spring 启用 factories 机制导入各个 starter 模块的配置。

原理剖析

通过下面的剖析咱们晓得 Spring 的 @EnableAutoConfiguration 次要性能是使 Spring 启用 factories 机制导入各个 starter 模块的配置。上面咱们会对 @EnableAutoConfiguration 的源码进行简略剖析。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

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

    String[] excludeName() default {};}

@EnableAutoConfiguration 注解的定义有两局部比拟重要的内容:
@AutoConfigurationPackage:将增加该注解的类所在的 package 作为主动配置 package 进行治理。
@Import({AutoConfigurationImportSelector.class}):用于导入 factories 文件中的 AutoConfiguration。

@Import({AutoConfigurationImportSelector.class})

首先咱们须要晓得 @Import 注解的作用,从字面意思就可以看进去,@Import 用于把一个 Bean 注入到 Spring 的容器中,@Import 能够导入三种类型的 Bean:

  1. 导入一般的 Bean,通常是 @Configuration 注解的 Bean,也能够是任意的 @Component 组件类型的类。
  2. 导入实现了 ImportSelector 接口的 Bean,ImportSelector 接口能够依据注解信息导入须要的 Bean。
  3. 导入实现了 ImportBeanDefinitionRegistrar 注解的 Bean,ImportBeanDefinitionRegistrar 接口能够间接向容器中注入指定的 Bean。

@Import({AutoConfigurationImportSelector.class})中的 AutoConfigurationImportSelector 实现了 ImportSelector 接口,会依照注解内容去装载须要的 Bean。

    public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {
            // 获取须要主动拆卸的 AutoConfiguration 列表
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            // 获取主动拆卸类的类名称列表
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

    // 获取须要主动拆卸的 AutoConfiguration 列表
    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {
            // 获取注解中的属性
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);

            // 获取所有 META-INF/spring.factories 中的 AutoConfiguration 类
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

            // 删除反复的类
            configurations = this.removeDuplicates(configurations);

            // 获取注解中 Execlud 的类
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);

            // 移除所有被 Exclude 的类
            configurations.removeAll(exclusions);

            // 应用 META-INF/spring.factories 中配置的过滤器
            configurations = this.getConfigurationClassFilter().filter(configurations);

            // 播送相干的事件
            this.fireAutoConfigurationImportEvents(configurations, exclusions);

            // 返回符合条件的配置类。return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

@AutoConfigurationPackage

@AutoConfigurationPackage 用于将增加该注解的类所在的 package 作为主动配置 package 进行治理,听起来是不是和 @ComponentScan 性能有所反复?咱们来剖析一下其具体实现,能够看到这个注解仍旧是通过 @Import 注解向容器中注册 Bean。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {String[] basePackages() default {};

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

@AutoConfigurationPackage注解导入了 Registrar.class,其本质是一个 ImportBeanDefinitionRegistrar,会把以后注解类所在的包注入到 Spring 容器中。

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {Registrar() { }

        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
        }

        public Set<Object> determineImports(AnnotationMetadata metadata) {return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
        }
}

@ComponentScan 并不会把类所在的包注入到容器中,@ComponentScan 只注入指定的包。类所在的包通过 @AutoConfigurationPackage 注入。

@Conditional 注解

@Conditional 注解的组件只有在满足特定条件的状况下才会被注册到容器中,@Conditional 注解的定义如下所示,能够看到这个注解只有一个内容:Condition,所以这个注解的重点就是 Condition 接口。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

    /**
     * All {@link Condition} classes that must {@linkplain Condition#matches match}
     * in order for the component to be registered.
     */
    Class<? extends Condition>[] value();

}

Condition 接口的定义如下所示,该接口只蕴含一个办法,输出以后的上下文信息和注解的参数,判断注解的 Bean 是否能够注册到 Spring 容器中。其中上下文信息蕴含了:Bean 定义管理器(BeanDefinitionRegistry)/BeanFactory/ 上下文环境 Environment/ 资源加载器 ResourceLoader/ 类加载器 ClassLoader。

@FunctionalInterface
public interface Condition {

    /**
     * Determine if the condition matches.
     * @param context the condition context
     * @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
     * or {@link org.springframework.core.type.MethodMetadata method} being checked
     * @return {@code true} if the condition matches and the component can be registered,
     * or {@code false} to veto the annotated component's registration
     */
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

@Conditional 判断机会

@Conditionl 注解作用于 Spring 读取 Bean 定义的阶段,这个阶段大多数 Bean 尚未实例化,多数实例化的 Bean 属于 Spring 的非凡 Bean,不能条件管制是否加载。

Spring 中的 Bean 有很多起源,如扫描包下的 Component、@Bean 注解的办法、@Import、用户手工注册 Bean 等办法注册 Bean,这些所有起源的 Bean 定义都能够应用 @Conditional 进行解决吗?答案是不是所有 Bean 定义起源都会应用 @Conditional 注解进行过滤,只有扫描包或者 @Configuration 注解类中的的 Bean 会应用 @Conditionl 注解进行判断。

  • 扫描包注册 Bean,咱们晓得基于注解的 Spring 容器会通过扫描包的模式去获取门路上面的 Component,这部分 Bean 定义是最早被加载到 Spring 容器中的。Spring 通过 AnnotatedBeanDefinitionReader 读取并注册 @Component 对应的 Bean 定义,其中 @Conditional 判断的局部逻辑如下:

        private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
                @Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
                @Nullable BeanDefinitionCustomizer[] customizers) {AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
            // 此处判断 Bean 是否应该被注册
            if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {return;}
            // 省略其它注册逻辑
        }
  • @Bean 办法注册,在 @Configuration 中的 @Bean 和不在 @Configuration 中的 @Bean 的模式不同,我在其它文章中有介绍,然而获取 Bean 定义的逻辑是统一的,@Bean 注解的办法是通过 ConfigurationClassBeanDefinitionReader 去读取的,其中也蕴含了对 @Conditional 注解的判断,其中局部要害判断代码如下所示。

        private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {ConfigurationClass configClass = beanMethod.getConfigurationClass();
            MethodMetadata metadata = beanMethod.getMetadata();
            String methodName = metadata.getMethodName();
    
            // Do we need to mark the bean as skipped by its condition?
            if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {configClass.skippedBeanMethods.add(methodName);
                return;
            }
            if (configClass.skippedBeanMethods.contains(methodName)) {return;}

    @Configuration 类中的 @Bean 注解是在 ConfigurationClassPostProcessor 中进行解析和注册的,这是一个BeanDefinitionRegistryPostProcessor,执行机会显然晚于扫描包的机会。

从 @Conditional 的判断原理能够看出,Spring 该当只容许 Bean 定义一个个进行注册,并且要严格保障读取程序,不容许 Bean 定义的批量注册。

@Conditional 扩大

为了简化用户的应用,Spring 提供了几种常见的 @Conditional 的实现,咱们下文中会介绍常见的几种实现。

  • @ConditionalOnBean 注解,当特定的 Bean 注解存在的时候注册以后 Bean,应用这个注解的时候要注解 Bean 的先后顺序,因为 @ConditionalOnBean 会去以后容器中查找是否有满足条件的 Bean,后注册的 Bean 会受先注册 Bean 的影响。
  • @ConditionalOnClass 注解,以后类门路中蕴含指定类的时候注册以后 Bean。
  • @ConditionalOnMissingBean 注解,当指定类型的 Bean 不存在的时候注册以后 Bean。
  • @ConditionalOnMissingClass 注解,以后类门路中不蕴含指定类的时候注册以后 Bean。
  • @ConditionalOnProperty 注解,指定属性满足特定值是失效。

值得一提的是,@Profile 注解自身也是应用 @Conditional 注解进行 Bean 的条件注册的。

@Conditional(ProfileCondition.class)

public @interface Profile {

/**

* The set of profiles for which the annotated component should be registered.

*/

String[] value();

}

@ComponentScan 组件

有些状况下,咱们的组件不都定义在带有 @EnableAutoConfiguration 注解的类对应的包上面,这个时候 @AutoConfigurationPackage 扫描的类就不能满足用户的需要了。Spring 提供了 @ComponentScan 组件让用户增加指定包上面的 Spring 组件到 Spring 容器中。

Filter 选项

扫描包过程中,Spring 容许用户依照条件过滤所须要的 Bean,@SpringBootApplication 中自身蕴含了 @ComponentScan 注解,并为注解配置了两个 Filter:TypeExcludeFilter 和 AutoConfigurationExcludeFilter 注解。这两个注解容许用户实现特定的类,从而实现对 Bean 定义的过滤。

@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 {// 省略属性}

@Import 原理

通过下面的内容,咱们晓得 @Import 在 Spring 的主动拆卸中有很重要的作用,用于主动拆卸过程中导入指定的配置类。接下来咱们剖析一下 @Import 注解的源码及其作用机制。

@Import 的解析仍旧是在要害类 ConfigurationClassPostProcessor 中进行的,ConfigurationClassPostProcessor蕴含了 @Bean、@Import 等注解的解析。

    private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
            boolean checkForCircularImports) {if (importCandidates.isEmpty()) {return;}

        if (checkForCircularImports && isChainedImportOnStack(configClass)) {this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }
        // 其它逻辑
    }

本文最先公布至微信公众号,版权所有,禁止转载!

正文完
 0