写点什么

SpringBoot 自动装配原理分析,手写 starter 组件

  • 2021 年 11 月 11 日
  • 本文字数:2749 字

    阅读完需:约 9 分钟

@SpringBootConfiguration 注解


这个注解我们点进去就可以发现,它实际上就是一个 @Configuration 注解,这个注解大家应该很熟悉了,加上这个注解就是为了让当前类作为一个配置类交由 Spring 的 IOC 容器进行管理,因为前面我们说了,SpringBoot 本质上还是 Spring,所以原属于 Spring 的注解 @Configuration 在 SpringBoot 中也可以直接应用。


@ComponentScan 注解


这个注解也很熟悉,用于定义 Spring 的扫描路径,等价于在 xml 文件中配置 context:component-scan,假如不配置扫描路径,那么 Spring 就会默认扫描当前类所在的包及其子包中的所有标注了 @Component,@Service,@Controller 等注解的类。


@EnableAutoConfiguration


这个注解才是实现自动装配的关键,点进去之后发现,它是一个由 @AutoConfigurationPackage 和 @Import 注解组成的复合注解。



@EnableXXX 注解也并不是 SpringBoot 中的新注解,这种注解在 Spring 3.1 版本就开始出现了,比如开启定时任务的注解 @EnableScheduling 等。


@Import 注解


这个注解比较关键,我们通过一个例子来说明一下。


定义一个普通类 TestImport,不加任何注解,我们知道这个时候这个类并不会被 Spring 扫描到,也就是无法直接注入这个类:


public class TestImport {}


现实开发中,假如就有这种情况,定义好了一个类,即使加上了注解,也不能保证这个类一定被 Spring 扫描到,这个时候该怎么做呢?


这时候我们可以再定义一个类 MyConfiguration,保证这个类可以被 Spring 扫描到,然后通过加上 @Import 注解来导入 TestImport 类,这时候就可以直接注入 TestImport 了:


@Configuration@Import


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


(TestImport.class)public class MyConfiguration {}


所以这里的 @Import 注解其实就是为了去导入一个类?AutoConfigurationImportSelector,接下来我们需要分析一下这个类。


AutoConfigurationImportSelector 类


进入这个类之后,有一个方法,这个方法很好理解,首先就是看一下 AnnotationMetadata(注解的元信息),有没有数据,没有就说明没导入直接返回一个空数组,否则就调用 getAutoConfigurationEntry 方法:



进入 getAutoConfigurationEntry 方法:



这个方法里面就是通过调用?getCandidateConfigurations 来获取候选的 Bean,并将其存为一个集合,最后经过去重,校验等一系列操作之后,被封装成 AutoConfigurationEntry 对象返回。


继续进入?getCandidateConfigurations 方法,这时候就几乎看到曙光了:



这里面再继续点击去就没必要了,看错误提示大概就知道了,loadFactoryNames 方法会去 META-INF/spring.factories 文件中根据 EnableAutoConfiguration 的全限定类名获取到我们需要导入的类,而 EnableAutoConfiguration 类的全限定类名为?org.springframework.boot.autoconfigure.EnableAutoConfiguration,那么就让我们打开这个文件看一下:



可以看到,这个文件中配置了大量的需要自动装配的类,当我们启动 SpringBoot 项目的时候,SpringBoot 会扫描所有 jar 包下面的 META-INF/spring.factories 文件,并根据 key 值进行读取,最后在经过去重等一些列操作得到了需要自动装配的类。


需要注意的是:上图中的 spring.factories 文件是在 spring-boot-autoconfigure 包下面,这个包记录了官方提供的 stater 中几乎所有需要的自动装配类,所以并不是每一个官方的 starter 下都会有 spring.factories 文件。


谈谈 SPI 机制


通过 SpringFactoriesLoader 来读取配置文件 spring.factories 中的配置文件的这种方式是一种 SPI 的思想。那么什么是 SPI 呢?


SPI,Service Provider Interface。即:接口服务的提供者。就是说我们应该面向接口(抽象)编程,而不是面向具体的实现来编程,这样一旦我们需要切换到当前接口的其他实现就无需修改代码。


在 Java 中,数据库驱动就使用到了 SPI 技术,每次我们只需要引入数据库驱动就能被加载的原因就是因为使用了 SPI 技术。


打开 DriverManager 类,其初始化驱动的代码如下:



进入 ServiceLoader 方法,发现其内部定义了一个变量:


private static final String PREFIX = "META-INF/services/";


这个变量在下面加载驱动的时候有用到,下图中的 service 即 java.sql.Driver:



所以就是说,在数据库驱动的 jar 包下面的 META-INF/services/ 下有一个文件 java.sql.Driver,里面记录了当前需要加载的驱动,我们打开这个文件可以看到里面记录的就是驱动的全限定类名:



@AutoConfigurationPackage 注解


从这个注解继续点进去之后可以发现,它最终还是一个 @Import 注解:



这个时候它导入了一个 AutoConfigurationPackages 的内部类 Registrar, 而这个类其实作用就是读取到我们在最外层的 @SpringBootApplication 注解中配置的扫描路径(没有配置则默认当前包下),然后把扫描路径下面的类都加到数组中返回。


手写一个 stater 组件

了解完自动装配的原理,接下来就可以动手写一个自己的 starter 组件了。


starter 组件命名规则


SpringBoot 官方的建议是,如果是我们开发者自己开发的 starter 组件(即属于第三方组件),那么命名规范是{name}-spring-boot-starter,而如果是 SpringBoot 官方自己开发的组件,则命名为 spring-boot-starter-{name}`。


当然,这只是一个建议,如果非不按这个规则也没什么问题,但是为了更好的识别区分,还是建议按照这个规则来命名。


手写 starter


写一个非常简单的组件,这个组件只做一件事,那就是实现 fastjson 序列化。


  • 新建一个 SpringBoot 应用 lonelyWolf-spring-boot-starter。

  • 修改 pom 文件,并新增 fastjson 依赖(省略了部分属性)。


<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.0</version><relativePath/></parent>


<groupId>com.lonely.wolf.note</groupId><artifactId>lonelyWolf-spring-boot-starter</artifactId><version>1.0.0-SNAPSHOT</version>


<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency>


<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.72</version></dependency></dependencies>


  • 新建一个序列化类 JsonSerial 类来实现 fastjson 序列化。


public class JsonSerial {public <T> String serial(T t){return JSONObject.toJSONString(t);}}


  • 新建一个自动装配类 MyAutoConfiguration 来生成 JsonSerial。


@Configurationpublic class MyAutoConfiguration {


@Beanpublic JsonSerial jsonSerial(){return new JsonSerial();}}


  • 完成之后将其打成一个 jar 包,然后再另一个 SpringBoot 中引入依赖:

评论

发布
暂无评论
SpringBoot自动装配原理分析,手写starter组件