SpringBoot自动装配核心原理(上)

SpringBoot自动装配核心原理(上)

 

在前面的章节里,笔者对Spring的核心内容做了比较详细的介绍,本章将对SpringBoot的自动化配置的核心原理进行详细介绍,在介绍本章内容之前,读者应该了解一些关于Spring的重点知识,包含@Import注解,ImportSelector接口,以及ImportBeanDefinitionRegistrar,这三个Spring拓展方式笔者在前面的章节做了详细的分析,并对源码也做了较为深入的分析,如果读者不清楚这部分内容的,不建议看本篇文章,因为SpringBoot的核心应用就是基于这些方式改造的,好了,废话不多说,我们开始进入正题。

首先新建一个SpringBoot项目,这里就不再SpringBoot源码工程写案例了,

package com.example.boot01;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ApplicationContext;@SpringBootApplicationpublic class Boot01Application {    public static void main(String[] args) {        ApplicationContext applicationContext = SpringApplication.run(Boot01Application.class, args);        String[] names=applicationContext.getBeanDefinitionNames();        for (String name : names) {            System.out.println(name);        }    }}

这里我们输出一下目前SpringBoot帮我们创建了多少个对象,

org.springframework.context.annotation.internalConfigurationAnnotationProcessororg.springframework.context.annotation.internalAutowiredAnnotationProcessororg.springframework.context.annotation.internalCommonAnnotationProcessororg.springframework.context.event.internalEventListenerProcessororg.springframework.context.event.internalEventListenerFactoryboot01Applicationorg.springframework.boot.autoconfigure.internalCachingMetadataReaderFactoryorg.springframework.boot.autoconfigure.AutoConfigurationPackagesorg.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfigurationpropertySourcesPlaceholderConfigurerorg.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfigurationspringApplicationAdminRegistrarorg.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfigurationorg.springframework.boot.autoconfigure.aop.AopAutoConfigurationorg.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfigurationapplicationAvailabilityorg.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfigurationorg.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessororg.springframework.boot.context.internalConfigurationPropertiesBinderFactoryorg.springframework.boot.context.internalConfigurationPropertiesBinderorg.springframework.boot.context.properties.BoundConfigurationPropertiesorg.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar.methodValidationExcludeFilterorg.springframework.boot.autoconfigure.context.LifecycleAutoConfigurationlifecycleProcessorspring.lifecycle-org.springframework.boot.autoconfigure.context.LifecyclePropertiesorg.springframework.boot.autoconfigure.info.ProjectInfoAutoConfigurationspring.info-org.springframework.boot.autoconfigure.info.ProjectInfoPropertiesorg.springframework.boot.autoconfigure.task.TaskExecutionAutoConfigurationtaskExecutorBuilderapplicationTaskExecutorspring.task.execution-org.springframework.boot.autoconfigure.task.TaskExecutionPropertiesorg.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfigurationtaskSchedulerBuilderspring.task.scheduling-org.springframework.boot.autoconfigure.task.TaskSchedulingPropertiesorg.springframework.aop.config.internalAutoProxyCreatorProcess finished with exit code 0

可以看到SpringBoot帮我们创建了很多很多的对象,看过笔者前面的章节的同学一定会觉得里面有一些类比较熟悉,有一些类比较陌生,比如什么后置处理器,比如什么事件监听等等,这里当然不会介绍他们,而是会介绍一个类,除了我们知道的类,我们还发现有这么一个类AutoConfigurationPackages,这个是从哪创建的呢?

我们看开这个注解,找到这个@EnableAutoConfiguration,然后再跟踪进去,可以看到有个@AutoConfigurationPackage注解,再跟踪进去,可以发现

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

这里就出现了@Import,笔者在前面的文章介绍过@Import有三种方式,这里就不再和大家一起复习了,这就是采用@Import的方式向SpringBoot注册了这个一个类AutoConfigurationPackages.class,我们再回到上一个注解

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

这里同样出现了@Import(AutoConfigurationImportSelector.class),@Import这么一种方式,而这个导入的类却不是一个普通类,

可以看到它实现了ImportSelector,也就是说Spring会去执行

String[] selectImports(AnnotationMetadata var1);

上面这个方法,我们写一个demo。然后导入这个类

public class TestImport implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        System.out.println("----------------");
        return new String[0];
    }
}
package com.example.boot01;import com.example.boot02.spi.Boot02_Test;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.AutoConfigurationPackage;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;import org.springframework.context.annotation.Import;@SpringBootApplication@Import(TestImport.class)public class Boot01Application {    public static void main(String[] args) {        //Boot02_Test boot02_test= new Boot02_Test();        ApplicationContext applicationContext= SpringApplication.run(Boot01Application.class, args);        /*String[] strings= applicationContext.getBeanDefinitionNames();        for (String string : strings) {            System.out.println(string);        }*/        //System.out.println(applicationContext.getBean(Boot02_Test.class));    }}

执行:

2020-12-19 17:53:15.755  INFO 5624 --- [           main] com.example.boot01.Boot01Application     : Starting Boot01Application using Java 1.8.0_201 on WIN-CMVDGH5C91O with PID 5624 (C:\Users\Administrator\IdeaProjects\SpringBoot\boot01\target\classes started by Administrator in C:\Users\Administrator\IdeaProjects\SpringBoot)2020-12-19 17:53:15.757  INFO 5624 --- [           main] com.example.boot01.Boot01Application     : No active profile set, falling back to default profiles: default----------------2020-12-19 17:53:16.071  INFO 5624 --- [           main] com.example.boot01.Boot01Application     : Started Boot01Application in 0.522 seconds (JVM running for 0.99)Process finished with exit code 0

可以看到代码已经被执行了。那么这个return new String[0];就是用来创建对象的,好了我们回到@Import(AutoConfigurationImportSelector.class),同样我们这里依旧会看到一个方法

但是SpringBoot会被增强执行另外一个方法,但是这都不重要,因为它始终会执行这样一段代码

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {    if (!isEnabled(annotationMetadata)) {      return EMPTY_ENTRY;    }    AnnotationAttributes attributes = getAttributes(annotationMetadata);    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);    configurations = removeDuplicates(configurations);    Set<String> exclusions = getExclusions(annotationMetadata, attributes);    checkExcludedClasses(configurations, exclusions);    configurations.removeAll(exclusions);    configurations = getConfigurationClassFilter().filter(configurations);    fireAutoConfigurationImportEvents(configurations, exclusions);    return new AutoConfigurationEntry(configurations, exclusions);  }

这里的代码十分重要,它就是SpringBoot自动装配的核心方法,我们调试一下

可以看到它获取了这么多类,这些类是从哪里来的?我们跟踪一下List<String> configurations = 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;  } 

然后我们可以看到这里做了解释就是说所有的类都是来自于 META-INF/spring.factories

这里就可以看到,确实是在这个文件找到的需要加载的类,然后我们回到代码

可以看到这里并不是所有的类都加载了,而是对配置类进行了筛选,为什么筛选?这也很好理解,因为我们并没有使用它,所以就不会加载到Spring容器里面,注意是加载到Spring容器,而这里的筛选是完成自动装配的重要操作,我会在下面的章节进行描述。好了可能读者还没体会到这段代码的牛逼之处,下面我将做一个案例让大家看看SpringBoot的设计到底有多精髓。

首先新建两个Boot项目

命名为boot1,boot2,然后在boot2上加一个类

/**
 * @author 七天0
 * @date 2020/11/29 0029 11:52
 * 概要 :
 */
@Component
public class Boot02_Test {


    @Bean
    public String string(){
        return "";
    }
}

这个很简单,就不说了,然后我们在对这个boot2打包,然后放在boo1下

然后这样我们就可以使用我们自己写的对象了,于是我们在boot1下进行测试

@SpringBootApplicationpublic class Boot01Application {    public static void main(String[] args) {        Boot02_Test boot02_test= new Boot02_Test();        ApplicationContext applicationContext= SpringApplication.run(Boot01Application.class, args);        /*String[] strings= applicationContext.getBeanDefinitionNames();        for (String string : strings) {            System.out.println(string);        }*/        System.out.println(applicationContext.getBean(Boot02_Test.class));    }}

执行代码:

2020-12-19 21:28:24.680  INFO 15472 --- [           main] com.example.boot01.Boot01Application     : Starting Boot01Application using Java 1.8.0_201 on WIN-CMVDGH5C91O with PID 15472 (C:\Users\Administrator\IdeaProjects\SpringBoot\boot01\target\classes started by Administrator in C:\Users\Administrator\IdeaProjects\SpringBoot)2020-12-19 21:28:24.682  INFO 15472 --- [           main] com.example.boot01.Boot01Application     : No active profile set, falling back to default profiles: default2020-12-19 21:28:25.024  INFO 15472 --- [           main] com.example.boot01.Boot01Application     : Started Boot01Application in 0.564 seconds (JVM running for 1.144)Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.boot02.spi.Boot02_Test' available  at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)  at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)  at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1179)  at com.example.boot01.Boot01Application.main(Boot01Application.java:22)

我们看到我们无法从容器找到我们自定义的Boot02_Test,但是我们可以自己手动new出来,道理很简单,这毕竟是我们自己添加的依赖,那么如果我们要将Boot02_Test交给我们的Spring管理,怎么办?只需要更改一下boot2的代码就可以了

然后我们再打包,在执行

2020-12-19 21:30:54.851  INFO 9456 --- [           main] com.example.boot01.Boot01Application     : Starting Boot01Application using Java 1.8.0_201 on WIN-CMVDGH5C91O with PID 9456 (C:\Users\Administrator\IdeaProjects\SpringBoot\boot01\target\classes started by Administrator in C:\Users\Administrator\IdeaProjects\SpringBoot)2020-12-19 21:30:54.854  INFO 9456 --- [           main] com.example.boot01.Boot01Application     : No active profile set, falling back to default profiles: default2020-12-19 21:30:55.168  INFO 9456 --- [           main] com.example.boot01.Boot01Application     : Started Boot01Application in 0.528 seconds (JVM running for 1.073)com.example.boot02.spi.Boot02_Test@4b41dd5c

这里就可以很清楚的看到我们的Boot02_Test已经交给了我们的Spring进行了管理。所以这也就解释了SpringBoot是加载所有maven里面的META-INF/spring.factories文件,也就是说SpringBoot会找到Maven下面的所有META-INF/spring.factories装在进入Spring容器,而这种装载是自动的,让然还没有结束,这里提出一个问题,我们可以看到这里面有很多的配置信息,那么我们是所有的东西都要添加吗?希望读者自行思考,我将会在下个章节做介绍。最后如果没有看懂@Import这一系列的,请一定要看前面的Spring源码章节,不然就无法理解为什么@Import这么厉害。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值