参考资料:
https://www.bilibili.com/video/BV1NY411P7VX?vd_source=0885d58575f3c82f15a5a40588fe0cd5
https://github.com/HelloWorld521/swagger2-boot-starter/tree/master/swagger2-boot-starter
https://www.cnblogs.com/progor/p/13297904.html
https://blog.csdn.net/cj151525/article/details/140099389
一, SpringBoot自动配置原理
自动装配和自动配置
自动配置(Auto-Configuration): 基于引入的依赖Jar包, 对SpringBoot应用进行自动配置.
自动装配(Autowire): Spring中的依赖注入.
另外, 配置类(Configuration Class)有广义和狭义之分:
- 广义: 被注解@Component直接或间接修饰的某个类, 常说的Spring组件, 其中包括了@Configuration类
- 狭义: 特指被注解@Configuration所修饰的某个类, 又称为@Configuration类
SpringBoot的启动流程
- 创建一个ApplicationContext实例, 即我们常说的IoC容器.
- 将主类(primaryClass)注册到IoC容器中(简单但重要的第一步). 源配置类: 通常是main方法所在的类, 会被@SpringBootApplication所修饰, 我们又称之为主类.
- 递归加载并处理所有的配置类. 自动配置就属于其中一环.
- 实例化所有的单例Bean(Singleton Bean). 实例化所有的单例Bean. 依赖注入和自动装配就属于其中的环节.
- 如果是web应用, 则启动web服务器(如Tomcat)
SpringBoot加载配置类的流程
- 处理@ComponentScan: 根据@ComponentScan扫描制定的package.
- 处理@Import: 得到一系列被导入的配置类
- 处理@Bean方法
- 处理@Import导入的ImportBeanDefinitionRegistrar
- 加入到一个全局的配置类集合中
- 将配置类本身注册到IoC容器中
- 处理配置类中的@Bean方法, 将其返回类型注册到IoC容器中
- 处理通过@Import导入的ImportBeanDefinitionRegistrar
SpringBoot加载配置类的方式
@ComponentScan: 对指定的package进行扫描, 找到符合条件的类, 默认是搜索被注解@Component修饰的配置类; 通过属性basePackages或basePackageClasses指定要进行扫描的package; 未指定package则默认扫描当前@ComponentScan所修饰的类所在的package.
例:
1@ComponentScan
2
3@ComponentScan(basePackages={"cn.anyuanwai.code", "cn.memset.code"})
4
5@ComponentScan(
6 excludeFilters = {@Filter(
7 type = FilterType.CUSTOM,
8 classes = {TypeExcludeFilter.class}
9), @Filter(
10 type = FilterType.CUSTOM,
11 classes = {AutoConfigurationExcludeFilter.class}
12)}
13)
@Import: 提供了一种显示地从其他地方加载配置类的方式, 这样可以避免使用性能较差得到组件扫描(Component Scan). 支持导入:
-
导入普通类(效果类似于这个普通类被注解@Component)
1@Configuration 2//ConfigA里有Bean方法. 3//这样之后ConfigA和ConfigB都可以作为Bean使用了. 4@Import(ConfigA.class) 5public class ConfigB{ 6 7}
-
导入选择器接口ImportSelector的实现类(ImportSelector有selectImports方法返回值是一个数组, 每一个元素分别代表一个将被导入的配置类的全限定名, 利用该特性我们可以给IoC容器动态地导入多个配置类)
1public class ZooImportSelector implements ImportSelector{ 2 @Override 3 public String[] selectImports(AnnotationMetadata metadata){ 4 return new String[]{"cn.memset.ZooConfig"}; 5 } 6}
1//配置类 2//这样ZooConfig和ZooConfig里面的Bean都在容器中. 3@Configuration 4@Import({ZooImportSelector.class}) 5public class ConfigB{ 6 7}
-
导入注册器接口ImportBeanDefinitionRegistrar的实现类(通过它可以手动将多个BeanDefinition注册到IoC容器, 实现个性化的定制)
1public class ZooRegistrar implements ImportBeanDefinitionRegistrar{ 2 @Override 3 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry){ 4 //手动注入Dog 5 bd.setBeanClass(Dog.class); 6 registry.registerBeanDefinition("dog", bd); 7 } 8}
1//配置类 2//这样Bean Dog在容器中. 3@Configuration 4@Import({ZooImportSelector.class}) 5public class ConfigB{ 6 7}
关于加载配置类这几种方式
- @ComponentScan需要记住jar包中的package名称, 不方便
- @Import导入普通类,需要记住第三方Jar包中具体的类名才能导入, 不方便
- @Import导入注册器ImportBeanDefinitionRegistrar是对@Bean方法的一个补充,针对BeanDefinition层面的.
- 只有ImportBeanDefinitionRegistrar导入选择器方便点.
SpringBoot自动配置原理剖析
@SpringBootApplication修饰的类会被@Configuration间接修饰, 即源配置类
SpringBoot框架会对源配置类的package进行组件扫描(ComponentScan)
SpringBoot框架最终会导入AutoConfigurationImportSelector来实现自动配置
AutoConfigurationImportSelector如何优雅实现自动配置呢? 用户只需导入jar包即可, 至于jar包有哪些自动配置类, 类名是什么都不用关心. 这恰好是Java SPI的优点. Spring框架中有个SpringFactories机制, 它是Java SPI设计思想的延伸和扩展, 自动配置就借助它实现.
SpringFactories机制
核心逻辑是从classpath中读取所有Jar包中的配置文件META_INF/spring.factories, 然后根据key从配置文件中解析处对应的value.
通过类SpringFactoriesLoader, 返回一个类名的集合, 可以根据实际需求对这些类名进行下一步处理.
当然这是spring2的机制, spring3中目录META_INF/spring.factories改变为/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
AutoConfigurationImportSelector整体逻辑
AutoConfigurationImportSelector中的selectImports方法中的getAutoConfigurationEntry是自动配置的入口方法. 在getAutoConfigurationEntry中:
- 获取annotationMetadata的注解@EnableAutoConfiguration的属性
- 从资源文件spring.factories中获取EnableAutoConfiguration对应的所有的类(getCandidateConfigurations中基于是SpringFactories机制)
- 通过在注解@EnableAutoConfiguration设置exclude的相关属性, 可以排除指定的自动配置类
- 根据注解@Confitional来判断是否需要排除某些自动配置类
- 触发AutoConfiguration导入的相关时间.
总结
理解Redis自动配置
spring-boot-starter-data-redis依赖了spring-boot-starter和spring-data-redis(工具类库Jar包, 定义了RedisTemplate等常用类), spring-boot-starter依赖了spring-boot-autoconfigure是SpringBoot内置的自动配置列的Jar包, 包含了spring.factories文件, 这里面就有很多redis的自动配置类类名.
二, SpringBoot自动配置实战案例
什么是Starter
Starter是一站式服务(one-step)的依赖jar包:
- 包含Spring以及相关技术(比如Redis)的所有依赖
- 提供了自动配置的功能, 开箱即用
- 提供了良好的依赖管理, 避免了包遗漏, 版本冲突的问题
Starter的结构图
Starter项目主要由两个模块构成: starter module和autoConfigure module(里面又包括自动配置类, 配置文件spring.factories, 自定义的配置项). starter module会依赖第三方组件的Jar包, 而autoConfigure module是可选依赖(Optional)第三方Jar包的.
可选依赖(Optional的作用: 阻断依赖传递.
如项目A可选依赖项目B, 那么使用Maven编译项目A时, 会将项目B添加到项目A的classpath中 ,此时可选依赖和普通依赖的表现是一致的
这时如果项目X依赖于项目A, 使用Maven编译项目X时, 项目B是不会被添加到项目X的classpath中的, 除非项目X直接依赖项目B.
Swagger的配置类
添加依赖包:
1 <dependency>
2 <groupId>io.springfox</groupId>
3 <artifactId>springfox-swagger2</artifactId>
4 <version>2.9.2</version>
5 </dependency>
6 <dependency>
7 <groupId>io.springfox</groupId>
8 <artifactId>springfox-swagger-ui</artifactId>
9 <version>2.9.2</version>
10 </dependency>
配置类:
1package com.example.config;
2
3import org.springframework.context.annotation.Bean;
4import org.springframework.context.annotation.Configuration;
5import springfox.documentation.builders.ApiInfoBuilder;
6import springfox.documentation.builders.PathSelectors;
7import springfox.documentation.builders.RequestHandlerSelectors;
8import springfox.documentation.service.ApiInfo;
9import springfox.documentation.spi.DocumentationType;
10import springfox.documentation.spring.web.plugins.Docket;
11import springfox.documentation.swagger2.annotations.EnableSwagger2;
12
13
14@Configuration // 标明是配置类
15@EnableSwagger2 //开启swagger功能
16public class SwaggerConfig {
17 @Bean
18 public Docket createRestApi() {
19 return new Docket(DocumentationType.SWAGGER_2) // DocumentationType.SWAGGER_2 固定的,代表swagger2
20// .groupName("分布式任务系统") // 如果配置多个文档的时候,那么需要配置groupName来分组标识
21 .apiInfo(apiInfo()) // 用于生成API信息
22 .select() // select()函数返回一个ApiSelectorBuilder实例,用来控制接口被swagger做成文档
23 .apis(RequestHandlerSelectors.basePackage("com.example.controller")) // 用于指定扫描哪个包下的接口
24 .paths(PathSelectors.any())// 选择所有的API,如果你想只为部分API生成文档,可以配置这里
25 .build();
26 }
27
28 /**
29 * 用于定义API主界面的信息,比如可以声明所有的API的总标题、描述、版本
30 * @return
31 */
32 private ApiInfo apiInfo() {
33 return new ApiInfoBuilder()
34 .title("XX项目API") // 可以用来自定义API的主标题
35 .description("XX项目SwaggerAPI管理") // 可以用来描述整体的API
36 .termsOfServiceUrl("") // 用于定义服务的域名
37 .version("1.0") // 可以用来定义版本。
38 .build(); //
39 }
40}
41
进行Swagger的自动配置
spring.factories:
1org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
2com.moming.swagger.autoconfigure.Swagger2AutoConfiguration
配置文件:
1package com.moming.swagger.properties;
2
3import lombok.Data;
4import org.springframework.boot.context.properties.ConfigurationProperties;
5import org.springframework.context.annotation.Configuration;
6import org.springframework.context.annotation.DependsOn;
7import org.springframework.context.annotation.Import;
8import org.springframework.stereotype.Component;
9
10import java.util.LinkedHashMap;
11import java.util.Map;
12
13/**
14 * 读取 application 配置文件
15 *
16 * @author hjy
17 **/
18@Data
19@Component
20@ConfigurationProperties("swagger2")
21public class Swagger2Properties {
22
23 /**
24 * 分组
25 */
26 private Map<String, GroupInfo> groups = new LinkedHashMap<>();
27
28
29 @Data
30 public static class GroupInfo {
31 /**
32 * swagger 会解析的包路径
33 */
34 private String basePackage;
35
36 /**
37 * title
38 */
39 private String title;
40
41 /**
42 * swagger 组相关说明
43 */
44 private String description;
45 }
46}
47
自动配置类:
1package com.moming.swagger.autoconfigure;
2
3import com.moming.swagger.properties.Swagger2Properties;
4import org.springframework.beans.BeansException;
5import org.springframework.beans.factory.BeanFactory;
6import org.springframework.beans.factory.BeanFactoryAware;
7import org.springframework.beans.factory.config.ConfigurableBeanFactory;
8import org.springframework.context.annotation.Bean;
9import org.springframework.context.annotation.Configuration;
10import springfox.documentation.builders.ApiInfoBuilder;
11import springfox.documentation.builders.PathSelectors;
12import springfox.documentation.builders.RequestHandlerSelectors;
13import springfox.documentation.service.ApiInfo;
14import springfox.documentation.spi.DocumentationType;
15import springfox.documentation.spring.web.plugins.Docket;
16import springfox.documentation.swagger2.annotations.EnableSwagger2;
17
18import java.util.ArrayList;
19import java.util.List;
20
21/**
22 * 1. 读取配置文件
23 * 2. 将配置文件内容赋值到 swagger 的 Docket 对象
24 * 3. 将 Docket 依次注入到 bean 中 (需要实现 BeanFactoryAware 获取beanFatory)
25 * 关键是对组的操作
26 *
27 * @author hjy
28 **/
29@Configuration
30@EnableSwagger2
31public class Swagger2AutoConfiguration implements BeanFactoryAware {
32
33 private BeanFactory beanFactory;
34
35 /**
36 * 获取 beanFactroy
37 * @param beanFactory beanFactory
38 * @throws BeansException BeansException
39 */
40 @Override
41 public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
42 this.beanFactory = beanFactory;
43 }
44
45 @Bean
46 public Swagger2Properties properties() {
47 return new Swagger2Properties();
48 }
49
50
51 @Bean("createRestOpenApi")
52 public List<Docket> createRestOpenApi(Swagger2Properties properties) {
53 ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;
54 List<Docket> docketList = new ArrayList<>();
55 // swagger 配置
56 for (String groupName : properties.getGroups().keySet()) {
57 Swagger2Properties.GroupInfo groupInfo = properties.getGroups().get(groupName);
58 String basePackage = groupInfo.getBasePackage();
59 Docket docket = new Docket(DocumentationType.SWAGGER_2)
60 .groupName(groupName)
61 .apiInfo(openApiInfo(groupInfo))
62 .select()
63 .apis(RequestHandlerSelectors.basePackage(basePackage))
64 .paths(PathSelectors.any())
65 .build();
66 docketList.add(docket);
67 // docket 加入 IOC 容器
68 configurableBeanFactory.registerSingleton(groupName, docket);
69 }
70 return docketList;
71 }
72
73
74 private ApiInfo openApiInfo(Swagger2Properties.GroupInfo groupInfo) {
75 return new ApiInfoBuilder()
76 .title(groupInfo.getTitle())
77 .description(groupInfo.getDescription())
78 .version("1.0")
79 .build();
80 }
81
82
83}
84
他这里相比普通的配置多了些东西. 它实现了BeanFactoryAware接口, 这样Spring 容器会在 bean 的初始化 过程中自动调用这个方法,并传入当前的 BeanFactory 实例, 然后就可以利用BeanFactory将docket加入到ioc容器了.
可能是它这里为了便捷配置接口组, 所以采用的是List形式.