SpringBoot自动装配源码分析


一、SpringBoot项目

// 这个注解实现了SpringBoot的包扫描及自动装配
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
    	 // 这个run方法点进去会发现里面执行到的是spring中的refresh()方法
    	 // 刷新ioc容器,进行bean的初始化
        SpringApplication.run(DemoApplication.class, args);
    }
}

二、@SpringBootApplication注解

点进@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 {
}

前四个注解是java中的四种元注解,主要作用如下:
@Target表示该注解用于什么地方,可以在枚举类 ElemenetType 中,包括:

 ElemenetType.CONSTRUCTOR ------------------- 构造器声明 
 ElemenetType.FIELD ------------------------- 域声明(包括 enum 实例) 
 ElemenetType.LOCAL_VARIABLE ---------------- 局部变量声明 
 ElemenetType.METHOD ------------------------ 方法声明 
 ElemenetType.PACKAGE ----------------------  包声明 
 ElemenetType.PARAMETER --------------------- 参数声明 
 ElemenetType.TYPE ---------------------------类,接口(包括注解类型)或enum声明 

@Retention 表示在什么级别保存该注解信息。可选的参数值在枚举类型 RetentionPolicy 中,包括:

 RetentionPolicy.SOURCE----------------------注解将被编译器丢弃 
 RetentionPolicy.CLASS ----------------------注解在class文件中可用,但会被VM丢弃 
 RetentionPolicy.RUNTIME --------------------VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息。

@Documented 将此注解包含在 javadoc 中 ,它代表着此注解会被javadoc工具提取成文档。在doc文档中的内容会因为此注解的信息内容不同而不同。

@Inherited 允许子类继承父类中的注解。

本节主要分析后三个注解@SpringBootConfiguration,@EnableAutoConfiguration与@ComponentScan。

1.@SpringBootConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

点进来发现它又是个组合注解,由@Configuration与@Indexed组成。
@Configuration用于定义配置类,替换了spring中的xml配置文件,详细请见日后spring源码中分析。
@Indexed是Spring 5.0版本新加入的功能,在很多应用中,随着应用变得越来越大,就会出现启动变得非常慢的问题,可以通过@Indexed来提高启动效率。

在项目中使用了@Indexed之后,编译打包的时候会在项目中自动生成META-INT/spring.components文件。
当Spring应用上下文执行ComponentScan扫描时,META-INT/spring.components将会被CandidateComponentsIndexLoader 读取并加载,转换为CandidateComponentsIndex对象,这样的话@ComponentScan不在扫描指定的package,而是读取CandidateComponentsIndex对象,从而达到提升性能的目的。

引入依赖即可使用。

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>5.1.12.RELEASE</version>
        <optional>true</optional>
    </dependency>
</dependencies>

component此时读取文件是只需要读一次这个文件后即可加载完成,不需要多次IO操作,由此提升了程序的启动速度。

@Indexed 并非可以任意使用。在没有其他模块依赖或者所依赖的模块都生成了 spring.components 文件时不会存在问题,然而如果依赖的模块只有部分模块存在 spring.components 文件,则其他模块的 bean 也不会被扫描,为避免这种问题,需要在类路径下 spring.properties 文件中或系统属性中的 spring.index.ignore 参数设置为 true,这样就会跳过 spring.components 文件的扫描,而转为重新扫描类路径下的 bean 。

2.@EnableAutoConfiguration

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

点进@EnableAutoConfiguration发现它由@AutoConfigurationPackage与@Import组成。

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

再点进去@AutoConfigurationPackage发现它是由@Import组成,所以问题来到了@Import上面。

我们先打开AutoConfigurationImportSelector.class这个类,看看里面的实现是什么样子的。
在这里插入图片描述它主要实现了Aware接口和ImoprtSelector接口,Aware部分不是此文章关键,在之后的spring源码中会提到。
那么重点来到了ImportSelecer接口的实现。

// 这个方法返回的就是需要注册到IoC容器中的对象对应的类型的全类路径名称的字符串数组
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

那么这个数据又是从哪来的呢,点进this.getAutoConfigurationEntry(annotationMetadata),

    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            // 获取注解的属性信息
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            // 获取候选配置信息,加载的是当前项目的classpath目录下的所有的META-INF/spring.factories文件中的key为
            // org.springframework.boot.autoconfigure.EnableAutoConfiguration的信息
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            // 移除集合中重复的元素,因为会加载多个路径下的spring.factories所以可能会重复
            configurations = this.removeDuplicates(configurations);
            // 将不需要配置的信息拿出来@SpringBootApplication(exclude = {xxx.class})
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            // 将这些不用的配置信息过滤,那么怎么确定哪些配置是不需要加载的呢?就需要打开filter方法看看
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        // 这里写明数据是从 META-INF/spring.factories取得,或者从上面的loadFactoryNames()方法进去也可找到获取数据的位置
        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;
    }

打开filter()方法之后,会看到如下
在这里插入图片描述打开autoConfigurationMetadata会发现它的数据来自于META-INF/spring-autoconfigure-metadata.properties。
在这里插入图片描述那我们打开/spring-autoconfigure-metadata.properties,拿出一个未过滤前的类org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

在这里插入图片描述

执行完filter方法后我们会发现这个配置被过滤掉了。

在这里插入图片描述
通过上面的配置文件,我们发现RedisAutoConfiguration 被注入到IOC容器中的条件是系统中要存在 org.springframework.data.redis.core.RedisOperations 这个class文件。
然而现在我们的项目里并没有这个文件。那我们就那这个配置文件里的路径创建一个空的class文件出来,试试还会不会被过滤掉
在这里插入图片描述答案是不会被过滤掉了。那么到此,这个自动装配的流程大概就清楚了。

3.@ComponentScan

@ComponentScan注解,该注解的作用是用来指定扫描路径的,如果不指定特定的扫描路径的话,扫描的路径是当前修饰的类所在的包及其子包。

三、手写Starter

我们如果想自己实现Starter呢,可以新创建一个Maven项目,创建配置类及Bean,进行打包。
在这里插入图片描述然后可以通过Maven依赖导入,让SpringBoot在启动容器时自动装配它。
在这里插入图片描述我们在原项目上引入依赖,启动run方法后会发现报错了?找不到MyService类,为什么呢?
在这里插入图片描述错误原因是因为SpringBoot项目的包路径与自己写的Starter路径不同
在这里插入图片描述
那我们就在自己写的Starter里面配置一下在这里插入图片描述
这样SpringBoot启动时,就会自动装配我们的Bean啦。

![在这里插入图片描述](https://img-blog.csdnimg.cn/ab72572730344a33861bfde0f7130d17.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5Zyo5LiL5bCP5a6J,size_20,color_FFFFFF,t_70,g_se,x_1

总结

本文主要分析了SpringBoot中用到的注解及SpringBoot自动装配的原理,还实现了自己的Starter。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值