SpringBoot是对Spring的一种扩展,其中比较重要的扩展功能就是自动装配:通过注解对常用的配置做默认配置,简化xml配置内容。本文会对Spring的自动配置的原理和部分源码进行解析,本文主要参考了Spring的官方文档。

自动装配的组件

SpringBoot自动装配通过多部分组件协调完成,这些组件主要有下面几种,这几种组件之间协调工作,最终完成了SpringBoot的自动装配。

@EnableAutoConfiguration:用于根据用户所引用的jar包自动装配Spring容器,比如用户在ClassPath中包含了HSQLDB,但是没有手动配置数据库连接,那么Spring会自动使用HSQLDB作为数据源。@Condition:不同情况下按照条件进行装配,Spring的JdbcTemplate是不是在Classpath里面?如果是,并且DataSource也存在,就自动配置一个JdbcTemplate的Bean@ComponentScan:扫描指定包下面的@Component注解的组件。@EnableAutoConfiguration注解

Spring的自动装配发展大致可以分为三个阶段:

全手工配置的XML文件阶段,用户需要的Bean全部需要在XML文件中声明,用户手工管理全部的Bean。半手工配置的注解阶段,用户可以安装需求Enable对应的功能模块,如添加@EnableWebMvc可以启用MVC功能。全自动配置的SpringBoot,用户只需要引入对应的starter包,Spring会通过factories机制自动装配需要的模块。

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

springboot md5盐加密_springboot集成shiro_springboot自动装配

半自动注解配置示意图:

springboot自动装配_springboot集成shiro_springboot md5盐加密

全自动注解配置示意图:

springboot md5盐加密_springboot集成shiro_springboot自动装配

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:

导入普通的Bean,通常是@Configuration注解的Bean,也可以是任意的@Component组件类型的类。导入实现了ImportSelector接口的Bean,ImportSelector接口可以根据注解信息导入需要的Bean。导入实现了ImportBeanDefinitionRegistrar注解的Bean,ImportBeanDefinitionRegistrar接口可以直接向容器中注入指定的Bean。

@Import({AutoConfigurationImportSelector.class})中的AutoConfigurationImportSelector实现了ImportSelector接口springboot自动装配,会按照注解内容去装载需要的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 configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            // 删除重复的类
            configurations = this.removeDuplicates(configurations);
            // 获取注解中Execlud的类
            Set 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 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[] value();
}

Condition接口的定义如下所示springboot自动装配,该接口只包含一个方法,输入当前的上下文信息和注解的参数,判断注解的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注解进行判断。

从@Conditional的判断原理可以看出,Spring应当只允许Bean定义一个个进行注册,并且要严格保证读取顺序,不允许Bean定义的批量注册。

@Conditional扩展

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

值得一提的是,@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 importCandidates, Predicate exclusionFilter,
            boolean checkForCircularImports) {
        if (importCandidates.isEmpty()) {
            return;
        }
        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }
        // 其它逻辑
    }

限时特惠:本站每日持续更新海量设计资源,一年会员只需29.9元,全站资源免费下载
站长微信:ziyuanshu688