SpringBoot自动装配原理分析和实现自定义启动器

1.概述

SpringBoot的自动装配,减少了原先需要编写xml配置文件或java配置类。本文将分析SpringBoot自动装配的原理(版本是2.3.5)。从注解的作用到源码分析,再到自定义SpringBoot启动器。

2.自动装配原理分析

自动装配的实现和这个注解有密切的关联。该注解是个复合注解,里面包含了三个关键的注解

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    在这里插入图片描述

本文先介绍@SpringBootConfiguration和@EnableAutoConfiguration两个注解,@ComponentScan注解放到下一篇博客。

重点分析@EnableAutoConfiguration注解的作用和实现原理。

2.1@SpringBootConfiguration

看到@Configuration注解表明该类是Spring的一个配置类

proxyBeanMethods = true 保证每个@Bean方法被调用多少次返回的组件都是单实例的

proxyBeanMethods = false 每个@Bean方法被调用多少次返回的组件都是新创建的
在这里插入图片描述
测试代码

  • proxyBeanMethods = true 默认就是true在这里插入图片描述
    控制台输出的结果如下:在这里插入图片描述
    获取两次对象都是同一个。

  • proxyBeanMethods = false在这里插入图片描述
    控制台输出的结果如下:在这里插入图片描述
    此时两个对象是不同的。

    扫描二维码关注公众号,回复: 13147070 查看本文章

2.2@EnableAutoConfiguration

开启自动装配功能的注解
在这里插入图片描述
exclude和excludeName 指定自动装配的类,不进行自动装配。

例如下面的测试代码:

@SpringBootApplication()
public class Boot02Application {
    
    
    public static void main(String[] args) {
    
    
        ConfigurableApplicationContext run = SpringApplication.run(Boot02Application.class, args);
        MybatisAutoConfiguration bean = run.getBean(MybatisAutoConfiguration.class);
        System.out.println(bean);
    }
}

没有设置exclude或者excludeName的时候,Spring容器当中存在MybatisAutoConfiguration对象。
在这里插入图片描述
将测试代码改成如下:

@SpringBootApplication(exclude = MybatisAutoConfiguration.class)
public class Boot02Application {
    
    
    public static void main(String[] args) {
    
    
        ConfigurableApplicationContext run = SpringApplication.run(Boot02Application.class, args);
        MybatisAutoConfiguration bean = run.getBean(MybatisAutoConfiguration.class);
        System.out.println(bean);
    }
}
// 或者 注意这里要写全限定类名
@SpringBootApplication(excludeName= "org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration")
public class Boot02Application {
    
    
    public static void main(String[] args) {
    
    
        ConfigurableApplicationContext run = SpringApplication.run(Boot02Application.class, args);
        MybatisAutoConfiguration bean = run.getBean(MybatisAutoConfiguration.class);
        System.out.println(bean);
    }
}

此时出现异常,MybatisAutoConfiguration不存在Spring容器当中
在这里插入图片描述
除此之外该注解其中最主要的两个注解是

@AutoConfigurationPackage 、@Import(AutoConfigurationImportSelector.class)

  • @AutoConfigurationPackage

    添加该注解的类所在的package 作为自动配置package 进行管理。

    所以SpringBoot启动类所在的package作为自动配置的package。

    配合@Import(AutoConfigurationPackages.Registrar.class)注解,将Registrar注入。Registrar实现了ImportBeanDefinitionRegistrar接口,该接口中的方法registerBeanDefinitions,用于Bean定义的注入,也就是这里将自动配置的package交给Spring管理。
    在这里插入图片描述

  • @Import(AutoConfigurationImportSelector.class)

    该注解在《Spring中@Import注解的使用和实现原理》博客中已经详细介绍过了,用于Bean的注入。在这里插入图片描述
    AutoConfigurationImportSelector实现了DeferredImportSelector接口,该接口继承自ImportSelector接口。我想当然的以为会执行selectImports方法,但是根据debug断点调试,并没有执行该方法。在这里插入图片描述
    这是为什么呢?

    在ConfigurationClassParser的processImports方法中有这样一段代码,如下:在这里插入图片描述
    当实现类,实现的接口是DeferredImportSelector的时候,执行if的逻辑。如果它只是实现了父接口ImportSelector,才会执行selectImports方法。

    handle方法如下:在这里插入图片描述
    此时deferredImportSelectors不为null,DeferredImportSelector的实现类被封装成DeferredImportSelectorHolder,添加到deferredImportSelectors当中。在这里插入图片描述
    看着集合的名字就有延迟注入的意思,在ConfigurationClassParser的parse方法中遍历完了所有的候选集,则会调用this.deferredImportSelectorHandler.process()。在这里插入图片描述
    process方法如下:在这里插入图片描述
    遍历deferredImportSelectors集合,每个都调用register方法。这里将AutoConfigurationImportSelector的内部类AutoConfigurationGroup添加到groupings集合当中,并将对应的配置类添加到configurationClasses当中。在这里插入图片描述
    两个Map的结果如下:在这里插入图片描述
    遍历完deferredImportSelectors之后,调用handler.processGroupImports()在这里插入图片描述
    遍历之前放在groupings中的DeferredImportSelectorGrouping对象,调用getImports方法,该方法返回的是延迟注入的类名封装成的Entry结点的迭代器对象。在这里插入图片描述
    遍历延迟注入类,调用process方法处理,该方法得到自动配置结点,将其添加到autoConfigurationEntries集合当中。再遍历自动配置结点的所有配置类的类名,添加到entries集合当中。在这里插入图片描述
    getAutoConfigurationEntry方法用于得到autoConfigurationEntry,该对象管理了自动配置的类名。getAutoConfigurationEntry方法代码如下:在这里插入图片描述
    这里的attributes是根据注解的元数据得到的两个属性exclude和excludeName,前面介绍过了,就是用来移除掉指定自动配置类的自动装配。

    调用getCandidateConfigurations方法得到候选自动配置类的类名。

    之后就是根据attributes来过滤得到的configurations。

    接着调用fireAutoConfigurationImportEvents,自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类,并触发该接口的onAutoConfigurationImportEvent方法。

    getCandidateConfigurations方法如下:在这里插入图片描述
    这里有个空判断言,意思是在META-INF/spring.factories下没有找到自动配置类,如果你使用了自定义的包,确保文件是正确的。

    说明要导入的自动配置类,都记录在了META-INF/spring.factories当中。随便打开一个该文件,就可以看到要自动装配的配置类。在这里插入图片描述
    loadFactoryNames方法如下:在这里插入图片描述
    此时缓存里竟然已经有了需要的东西,重新打断点,再一次调试发现。在Spring容器启动的时候,构造方法里面就已经会获取SpringFactoriesInstances,加载META-INF/spring.factories目录下的自动配置类。在这里插入图片描述
    loadSpringFactories方法如下:在这里插入图片描述
    FACTORIES_RESOURCE_LOCATION是一个常量,等于"META-INF/spring.factories",遍历目录下的该文件夹,例如下图是属于/Users/gongsenlin/MavenRespository/org/springframework/boot/spring-boot/2.3.5.RELEASE/spring-boot-2.3.5.RELEASE.jar!/META-INF/spring.factories文件的属性结果。在这里插入图片描述
    都添加到cache缓存当中。在这里插入图片描述

    继续回到getImports方法,上面分析完了process方法,接下来就是调用group的selectImports方法,返回那些应该被注入的类。在这里插入图片描述
    利用之前已经保存在autoConfigurationEntries和entries集合当中数据,筛选出要注入的类。在这里插入图片描述
    结果如下:在这里插入图片描述
    getImports()方法结束,回到了processGroupImports()在这里插入图片描述
    遍历上面的31个要注入的类,调用processImports方法。该方法在之前的博客《Spring中@Import注解的使用和实现原理》中分析过了,这里不再赘述,至此就完成了自动装配。

3.自定义启动器

根据上面的的分析,可以想到要实现自动装配,需要编写一个spring.factories文件,放在META-INF目录下。就可以将某个类,交给Spring来管理。

再配合上@Configuration注解、@Bean注解、@Import注解等,就可以实现简单的自动装配。

starter模块:只需要将其他自动配置模块,或依赖的模块添加到pom文件中。

如下:在这里插入图片描述
autoconfigure模块目录结构如下:
在这里插入图片描述
其中配置类是MyAutoConfigure,代码如下:在这里插入图片描述
使用@Configuration注解表明这是一个配置类,配合@Import注解和@Bean注解将A类和B类注入Spring容器当中。

META-INF/spring.factories文件不能少,内容如下:在这里插入图片描述
这样就完成了一个简单的自定义启动器。

将这个starter启动器的依赖,添加到需要的项目pom文件当中,就可以实现自动装配了。在这里插入图片描述
启动boot-02项目,A类和B类实现了自动装配。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/gongsenlin341/article/details/114076559
今日推荐