面试题: SpringBoot 的自动配置原理及定制starter
Posted wushaopei
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试题: SpringBoot 的自动配置原理及定制starter相关的知识,希望对你有一定的参考价值。
3、Spring Boot 的自动配置原理
上述是主启动类的代码,我们从中可以看到在类名前面添加了一个注解@SpringBootApplication;在前面的SpringBoot 的自启动配置一文中,我们可以得知,在主启动类中,main方法中的 SpringApplication.run(DemoApplication.class,args);是对主启动类进行初始化并启动IOC容器进行配置bean的管理。
在这里,我们可以发现,SpringBoot 中比 Spring 要优秀的一点 自动配置 的特性并没有表现在main方法中,从代码中,我们可以看出,也许 @SpringBootApplication 这一个注解会与SpringBoot 的自动配置有关,本文将从这方面下手,根据源码进行分析。
使用过Spring Boot 的我们都知道,SpringBoot 中有一个全局配置文件: application.properties 或 application.yml。
我们在项目开发过程中对程序配置的 端口、数据库等属性都可以在这个文件中进行配置,最常见的配置有: server.port 、logging.level.* 等等,然而我们实际用到的往往只是很少的一部分,那么这些属性是否有据可依呢?答案是肯定的,这些属性都可以在官方文档中查找到:
在Appendix A. Common application properties小节下:
我们对自动配置大胆做个猜测,在Spring Boot 中有个机制,它将通过pom.xml 依赖加载到的诸如 mybatis、mysql、activemq等的jar 包创建对应的 bean,然后将bean放到容器中,并通过在 application.properties 或 application.yml 文件中进行配置属性参数映射到对应的 bean 中的属性上,从而实现如mysql 的数据库链接地址、密码、用户名的配置等操作。
那么,在这里我们针对猜测的自动配置过程提出三个关键性问题,分别是:
- SpringBoot 中怎么将引入的依赖包的类创建bean并加入到容器中的?
- springboot 中怎么获取到配置文件 application.properties 或application.yml 中的属性参数?
- springboot 怎么将获取到的配置文件参数映射到bean中的属性上?
3.1 配置加载依赖并创建bean加入到容器中:
(1)引入 jar
spring-boot-starter-web 包自动帮我们引入了web模块开发需要的相关jar包,
mybatis-spring-boot-starter 帮我们引入了dao开发相关的jar包。
spring-boot-starter-xxx 是官方提供的starter,xxx-spring-boot-starter是第三方提供的starter。
如下图中:
从以下的截图可以看出在这个mybatis-spring-boot-starter 中,并没有任何源码,只有一个pom文件,它的作用就是帮我们引入了相关jar包。
在这里我们看一下SpringBoot 中数据源的配置方案:
在这里,starter机制帮我们完成了项目起步所需要的相关jar包。我们知道,传统的spring应用中需要在 application.xml 中配置很多bean的,比如 dataSource 的配置,transactionManager的配置...... springboot是如何帮我们完成这些bean的配置的呢?
自动配置
(2)基于java代码的bean的配置
以mybatis为例,引入的mybatis 包不只有一个pom.xml文件, 还有一个关键性的包:mybatis-spring-boot-autoconfigure这个包
从mybatis-spring-boot-autoconfigure包下的文件名我们可以知道MybatisAutoConfiguration类是自动配置的核心文件;我们进入该文件看看,
从截图中我们可以看到,类名和方法名前分别添加了@Configuration 、@Bean两个注解,了解Spring的都知道,这两个注解一起使用就可以创建一个基于java代码的配置类,可以用来替代相应的xml配置文件。
@Configuration注解的类可以看作是能生产让Spring IoC容器管理的Bean实例的工厂。
@Bean注解告诉Spring,一个带有@Bean的注解方法将返回一个对象,该对象应该被注册到spring容器中。
传统的基于xml的bean配置方法如下:
相当于用基于java代码的配置方式:
由此,我们可以知道,上面的 MybatisAutoConfiguration这个类,自动帮我们生成了SqlSessionFactory 这些 Mybatis 的重要实例并叫个spring容器管理,从而完成bean的自动注册。
(3)springboot 特有的常见条件依赖注解:
到这里,我们知道了,要想完成Mybatis的自动配置,需要在类路径中存在 SqlSessionFactory.class、SqlSessionFactoryBean.class 这两个类,同时也需要有DataSource这个bean存在且已经自动注册到容器中。
根据MybatisAutoConfiguration的名称我们可以得知,在Springboot 中,引入自动配置的DataSource的包的名称应该是 DataSourceAutoConfiguration;
通过查看该包所在目录我们知道这个包又属于spring-boot-autoconfigure-2.0.4.RELEASE.jar这个包,自动配置这个包帮们引入了jdbc、kafka、logging、mongo、quartz等包。很多包需要我们引入相应jar后自动配置才生效。
3.2 获取到配置文件 application.properties 或application.yml 中的参数
Bean 参数的获取
在这里,我们要解决的是怎么从 .properties或yml文件中获取参数,让springboot知道配置文件中有哪些参数?
在DataSourceAutoConfiguration类里面,我们注意到使用了EnableConfigurationProperties这个注解。在该注解中引入了 DataSourceProperties.class 这个字节码文件。
点击进入查看,DataSourceProperties中封装了数据源的各个属性,且使用了注解ConfigurationProperties指定了配置文件的前缀。
@EnableConfigurationProperties与@ConfigurationProperties这两个注解有什么用呢?我们先看一个例子:
运行结果:
从运行结果可以看出@ConfigurationProperties与@EnableConfigurationPropertie的作用就是:
@ConfigurationProperties注解的作用是把yml或者properties配置文件转化为bean。
@EnableConfigurationProperties注解的作用是使@ConfigurationProperties注解生效。如果只配置@ConfigurationProperties注解,在spring容器中是获取不到yml或者properties配置文件转化的bean的。
通过这种方式,把yml或者properties配置参数转化为bean,这些bean又是如何被发现与加载的?
3.3 发现bean 并进行加载
到了这里,我们解决了两个问题,知道了通过pom.xml加载到程序中的jar包是怎么被Springboot进行自动配置,也知道了Springboot中是怎么读取并使用 .properties或 .yml 文件的参数的。接下来我们需要将两者进行关联,参数Bean是怎么被发现,并加载到容器中与自动配置的bean进行关联映射的?
Bean的发现
从前面Springboot的自启动机制中可以知道,Springboot 默认扫描启动类所在的包下的主类与子类的所有组件,但并没有包括依赖包中的类,那么依赖包中的bean是如何被发现和加载的?
我们在前面说过,SpringBoot 的自动配置是由@SpringBootApplication这个注解起得作用,具体的实现原理我们点进去看一下:
在SpringBootApplication注解类中,实际上重要的只有三个Annotation:
@Configuration(@SpringBootConfiguration里面还是应用了@Configuration)
@EnableAutoConfiguration
@ComponentScan
@Configuration的作用上面我们已经知道了,被注解的类将成为一个bean配置类。
@ComponentScan的作用就是自动扫描并加载符合条件的组件,比如@Component和@Repository等,最终将这些bean定义加载到spring容器中。
@EnableAutoConfiguration 这个注解的功能很重要,借助@Import的支持,收集和注册依赖包中相关的bean定义。
这里进入到 @EnableAutoConfiguration 注解中看看:
如上源码,@EnableAutoConfiguration注解引入了@AutoConfigurationPackage和@Import这两个注解。@AutoConfigurationPackage的作用就是自动配置的包,@Import导入需要自动配置的组件。
进入@AutoConfigurationPackage,发现也是引入了@Import注解
点进去 @Import 引用的 Registrar.class 的Registrar类去看看:
从上述源码我们可以解决一个小困惑,如下:
这两句代码的作用是用来加载启动类所在的包下的主类与子类的所有组件注册到spring 容器,这就是前文所说的springboot默认扫描启动类所在的包下的主类与子类的所有组件。
这里进入AutoConfigurationPackages 包去看看,
简单介绍一下,AnnotationMetadata
有两种重要的实现方案,一种基于 Java 反射,另一种基于 ASM 框架。
两种实现方案适用于不同场景。StandardAnnotationMetadata
基于 Java 反射,需要加载类文件。而 AnnotationMetadataReadingVisitor
基于 ASM 框架无需提前加载类,所以适用于 Spring 应用扫描指定范围内模式注解时使用。
下面是metadata 的相关接口和类的关系图:
获取并返回包名,PackageImport
根据元数据中返回的类名获取其所在的包目录,将该包目录返回给容器,用于自定义扫描包或默认配置扫描。
回到原来的问题上,搜集并注册到spring 容器的那些bean来自哪里?
进入@EnableAutoConfiguration注解的EnableAutoConfigurationImportSelector 类中去看看,该类继承了AutoConfigurationImportSelector 类,进入该类进行看下:
在 selectImports 方法中,SpringFactoriesLoader.loadFactoryNames 方法扫描所有具有META-INF/spring.factories的jar包。spring-boot-autoconfigure-x.x.x.x.jar里就有一个这样的spring.factories文件。
注意:在启动loadFactoryNames()方法前,SpringFactoriesLoader.loadFactoryNames方法调用loadSpringFactories方法从所有的jar包中读取META-INF/spring.factories文件信息。
loadFactories会将加载到的jar中的类加载并保存,以提供loadFactoryNames中进行配置添加到执行序列
这个spring.factories文件也是一组一组的key=value的形式,其中一个key是EnableAutoConfiguration类的全类名,而它的value是一个xxxxAutoConfiguration的类名的列表,这些类名以逗号分隔,如下图所示:
这个@EnableAutoConfiguration注解通过@SpringBootApplication被间接的标记在了Spring Boot的启动类上。在SpringApplication.run(...)的内部就会执行selectImports()方法,找到所有JavaConfig自动配置类的全限定名对应的class,然后将所有自动配置类加载到Spring容器中。
其中有一个key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值定义了需要自动配置的bean,通过读取这个配置获取一组@Configuration类。
注意:每个xxxAutoConfiguration都是一个基于java的bean配置类。实际上,这些xxxAutoConfiguratio不是所有都会被加载,会根据xxxAutoConfiguration上的@ConditionalOnClass等条件判断是否加载。
如上代码段,通过反射机制将spring.factories中@Configuration类实例化为对应的java实列。到此我们已经知道怎么发现要自动配置的bean了,最后一步就是怎么样将这些bean加载到spring容器。
bean的加载
如果要让一个普通类交给Spring容器管理,通常有以下方法:
- 使用 @Configuration与@Bean 注解
- 使用@Controller @Service @Repository @Component 注解标注该类,然后启用@ComponentScan自动扫描
- 使用@Import 方法
springboot中使用了@Import 方法
@EnableAutoConfiguration注解中使用了@Import({AutoConfigurationImportSelector.class})注解,AutoConfigurationImportSelector实现了DeferredImportSelector接口,
DeferredImportSelector接口继承了ImportSelector接口,ImportSelector接口只有一个selectImports方法。
我们先通过一个简单例子看看@Import注解是如何将bean导入到spring容器的。
1、新建一个bean
2、创建一个UserSelecter类继承ImportSelector接口并实现selectImports方法
3、创建ImportConfig类,使用@Configuration、@Import(ItpscSelector.class)注解。
4、从容器获取bean
运行结果:
从结果可以看出:selectImports方法返回一组bean,@EnableAutoConfiguration注解借助@Import注解将这组bean注入到spring容器中,springboot正式通过这种机制来完成bean的注入的。
总结:
Spring Boot启动的时候会通过@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的所有自动配置类,并对其进行加载,而这些自动配置类都是以AutoConfiguration结尾来命名的,它实际上就是一个JavaConfig形式的Spring容器配置类,它能通过以Properties结尾命名的类中取得在全局配置文件中配置的属性如:server.port,而XxxxProperties类是通过@ConfigurationProperties注解与全局配置文件中对应的属性进行绑定的。
图片来自于王福强老师的博客:https://afoo.me/posts/2015-07-09-how-spring-boot-works.html
4、根据自动配置原理自定义一个 个性的 started ?
以上是关于面试题: SpringBoot 的自动配置原理及定制starter的主要内容,如果未能解决你的问题,请参考以下文章