springBoot自动配置原理源码分析+自定义starter启动器+可视化监控+mybatisPlus使用
Posted ahcfl
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springBoot自动配置原理源码分析+自定义starter启动器+可视化监控+mybatisPlus使用相关的知识,希望对你有一定的参考价值。
一、springBoot自动化配置原理
1. starter管理机制
通过依赖了解SpringBoot管理了哪些starter
- 通过依赖
spring-boot-dependencies
搜索starter-
发现非常多的官方starter,并且已经帮助我们管理好了版本。 - 项目中使用直接引入对应的
starter
即可,这个场景下需要的依赖就会自动导入到项目中,简化了繁琐的依赖。
所有的场景启动器都依赖于spring-boot-starter
例如druid:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.24</version>
<scope>compile</scope>
</dependency>
这个启动器本身没有代码,通过依赖,构建了springBoot的基础运行环境,
包括spring基础环境,自动化配置基本环境。
org\\springframework\\boot\\spring-boot-starter\\spring-boot-starter-2.5.0.pom 里面的默认依赖
自动化配置依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.5.0</version>
<scope>compile</scope>
</dependency>
spring的IOC容器
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.7</version>
<scope>compile</scope>
</dependency>
。。。。。等等
小结:
- 引入的官方starter依赖默认都可以不写版本
- 如果配置满足您当前开发需要,则默认配置即可
- 自定义的starter,需要引入spring-boot-starter
2. springmvc的自动化配置原理
以web MVC自动化配置原理为例,理解web MVC自动化配置加入了哪些依赖,做了哪些默认配置。
回忆一下:SpringMVC学习时候,我们在 SSM整合时,添加spring及spring web mvc相关依赖
springmvc.xml 配置文件配置了:
1. 扫描controller 所在包
2. 配置annotation-driven支持mvc功能(HandlerMapping, HandlerAdapter)
3. 视图解析器
4. 静态资源
5. 拦截器
6. ……
web.xml 配置:
1. 初始化spring容器
2. 初始化springmvc DispatcherServlet
3. post请求乱码过滤器
部署还需要单独的tomcat
-----------------------------------------------------
也就是说:我们现在需要在开发业务代码前,就必须要准备好这些环境,否则无法完成业务代码,
这就是我们现在的问题。
让这些问题成为过去,现在我们就探索一下SpringBoot是如何帮助我们完成强大而又简单自动化配置的。
以引入web启动器为列:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
小结: 有了SpringBoot以后,让开发人员重点关注业务本身,而不是环境上,提升了开发效率。
3. 底层原理之@Configuration
理解@Configuration的作用和新特性
@Configuration : 标注当前类是一个配置类,spring会加载改配置类
属性 proxyBeanMethods:
true: @Bean标注的方式创建 对象会使用代理方式创建,并且放到spring容器中,单例。
false: @Bean标注的方法执行调用来创建对象,不会进spring容器(多例)。
默认为true
步骤:
1.创建MyConfig配置类,提供方法创建对象,使用@Bean标注
2.创建User实体类,
3.通过spring容器获取配置类对象,调用方法获取对象。
代码演示:
package com.ahcfl.demo2.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false) // 标注当前类是一个配置类,spring会加载改配置类,且不会进spring容器(多例模式)。
public class MyConfig {
@Bean
public User user(){
return new User();
}
}
package com.ahcfl.demo2.pojo;
public class User {
public User() {
System.out.println("对象被创建了");
}
}
package com.ahcfl.demo2;
import com.ahcfl.demo2.config.MyConfig;
import com.ahcfl.demo2.pojo.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class Demo2Application {
public static void main(String[] args) {
ConfigurableApplicationContext ac = SpringApplication.run(Demo2Application.class, args);
MyConfig myConfig = ac.getBean(MyConfig.class);
User user = myConfig.user();
User user2 = myConfig.user();
System.out.println(user);
System.out.println(user2);
}
}
小结:不常用的bean设置为false,不加入IOC容器中,可以提升springBoot启动速度。
4. 底层原理之@Import
【1】@Import的基础用法
1.导入Bean,会自动执行当前类的构造方法创建对象,存到IOC容器, bean名称为:类的全路径
2.导入配置类,并且类中有 带有@Bean注解方法,创建对象存到IOC容器,bean名称为:默认方法名称
代码演示
package com.ahcfl.demo2;
import com.ahcfl.demo2.config.MyConfig;
import com.ahcfl.demo2.pojo.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
@SpringBootApplication
@Import(User.class)
@Import(MyConfig.class)
public class Demo2Application {
public static void main(String[] args) {
ConfigurableApplicationContext ac = SpringApplication.run(Demo2Application.class, args);
User user = ac.getBean(User.class);
System.out.println(user);
}
}
【2】@Import另外两种实现
为讲解源码做铺垫
-
导入 ImportSelector 实现类。会调用接口的selectImports()方法来加载资源。
-
导入 ImportBeanDefinitionRegistrar 实现类,会调用接口的registerBeanDefination()
来向spring注册bean的信息。(将对象放到spring容器中)
5. 底层原理之@Conditional衍生条件装配
作用:条件装配,满足Conditional指定的条件,则进行组件注入,初始化Bean对象到IOC容器
package com.ahcfl.demo2.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {
@ConditionalOnMissingBean(Dog.class) //在没有dog类对象的情况下创建bean
//@ConditionalOnClass(Dog.class) //存在当前类的情况下创建bean
@Bean
public User user(){
return new User();
}
}
小结:@ConditionalOnXXX 注解存在的意义是:满足条件当前类或者Bean才有效,按需导入。
6. 底层原理之@ConfigurationProperties配置绑定
在springBoot基础中有演示,用于配置文件的自动依赖注入。
7. 自动化配置原理@SpringBootApplication入口分析
理解SpringBoot自动化配置流程中@SpringBootApplication是一个组合注解,及每一个注解的作用
@SpringBootApplication组合注解
【1】@SpringBootConfiguration注解作用
-
@SpringBootConfiguration是对@Configuration注解的包装,
proxyBeanMethods 默认配置 true, full模式(单例模式创建Bean),反之,false为 多例模式
-
标识是一个配置类,所以 引导类也是配置类
【2】@ComponentScan注解作用
- 组件扫描,默认扫描的规则 ( 引导类所在的包及其子包所有带注解的类 )
问题:
- 在引导类中配置 @Bean 注解可以吗?
- 为什么Controller、service类添加完注解后,不需要添加扫描包?
【3】@EnableAutoConfiguration自动配置注解
理解@EnableAutoConfiguration自动化配置核心实现注解
@EnableAutoConfiguration也是一个组合注解
【1】@AutoConfigurationPackage
作用:利用Registrar给容器中导入一系列组件 ,将引导类的所有包及其子包的组件导入进来
点击 Registrar
进入到源码的 register
方法,添加 断点,测试
通过 debug 程序发现,默认情况下 将引导类的所有包及其子包的组件导入进来
【2】@Import(AutoConfigurationImportSelector.class)注解作用
作用:利用selectImports
方法中的 getAutoConfigurationEntry
方法给容器中批量导入工厂配置相关组件
- 调用
AutoConfigurationImportSelector
类中的selectImports
方法 - 调用
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)
获取到所有需要导入到容器中的配置类
- 利用工厂加载
Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader)
得到所有的组件 - 从META-INF/spring.factories位置来加载一个文件。
默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
小结: 自动化配置默认加载的配置文件在哪?
META-INF/spring.factories
8. 自动化配置原理-按条件开启自动配置类和配置项
- 理解所有的自动化配置虽然会全部加载,但由于底层有大量的@ConditionalOnXXX注解进行判断,所以有很多自动配置类并不能完全开启
- 如果配置生效了,则会加载默认的属性配置类,实现默认的对应场景的自动化配置
以上通过 META-INF/spring.factories
配置文件找到所有的自动化配置类,但 是不是全部的生效的呢?很显然是不可能全部都生效的。
以webmvc自动化配置为例
问题: 这些不用的 starter 的依赖,能不能导入到我们工程里面? 为什么?
导入相关的starter 依赖,才会进行自动配置加载。不用的不必要导入,会降低springBoot启动速度。
9. 自动化配置原理-springBoot源码分析(理解)
理解整个SpringBoot启动的完成自动化配置及属性加载的全过程
【1】 启动类分析
package com.ahcfl;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.ahcfl.mapper")
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class,args);
}
}
这里跟SpringBoot有关联的部分有两个,
一个是SpringApplication.run(WebApplication.class, args);
,
一个就是启动类上的注解:@SpringBootApplication
。
分别跟踪两部分内容。
【2】 springBoot启动过程
main函数中的SpringApplication.run(BankApplication.class, args);
就是项目的入口,
也是Spring加载的完整过程,我们从这里开始。
首先跟入run方法,流程如图:
因此,接下来要看的是两部分:
new SpringApplication(primarySources)
:构造函数初始化run(args)
:成员的run方法
[1] SpringApplication构造函数
我们把跟构造函数有关的几个变量和方法提取出来,方便查看:
// SpringApplication.java
/**
* 资源加载器,读取classpath下的文件
*/
private ResourceLoader resourceLoader;
/**
* SpringBoot核心配置类的集合,这里只有一个元素,是我们传入的主函数
*/
private Set<Class<?>> primarySources;
/**
* 当前项目的应用类型
*/
private WebApplicationType webApplicationType;
/**
* ApplicationContextInitializer 数组
*/
private List<ApplicationContextInitializer<?>> initializers;
/**
* ApplicationListener 数组
*/
private List<ApplicationListener<?>> listeners;
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
// 核心构造函数
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 1.记录资源加载器
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 2.将传入的启动类装入集合
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 3.判断当前项目的类型,可以是SERVLET、REACTIVE、NONE
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 4.初始化 initializers 数组
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 5.初始化 listeners 数组
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
分析说明:
-
ResourceLoader resourceLoader
:Spring中用来加载资源的加载器 -
Class<?>... primarySources
:这里是启动类,本例中就是WebApplication.class -
WebApplicationType.deduceFromClasspath()
:判断当前项目的类型,可以是SERVLET、REACTIVE、NONE,根据当前classpath中包含的class来判断,
影响后续创建的ApplicationContext的类型 【3】
-
getSpringFactoriesInstances(ApplicationContextInitializer.class)
:获取ApplicationContextInitializer类型的实现类对象数组 【4】 -
getSpringFactoriesInstances(ApplicationListener.class)
:获取ApplicationListener类型的实现类对象数组 【5】 -
deduceMainApplicationClass()
:没有实际用途,打印日志,输出当前启动类名称
我们只看难点部分,也就是步骤3、4、5
1)deduceFromClasspath()方法
判断项目类型:
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
可以看到判断结果包含3种:
- REACTIVE:要求classpath中包含
org.springframework.web.reactive.DispatcherHandler
,这个是WebFlux中的核心处理器,我们并没有。 - SERVLET:要求classpath中包含
org.springframework.web.servlet.DispatcherServlet
,这是SpringMVC的核心控制器,在classpath中肯定可以找到 - NONE:以上都不满足,就是NONE
2)getSpringFactoriesInstances()方法
在构造函数中被调用了两次,分别加载ApplicationContextInitializer
和ApplicationListener
:
getSpringFactoriesInstances(Class<T> type)
方法的作用是获得指定接口的实现类的实例集合。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
// 调用下面的一个重载方法,参数type就是接口的类型
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
// 真正的处理逻辑
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// 1.先加载指定接口的实现类的名称集合
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 2.根据类的名称,创建实例对象
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 3.排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
这里关键是第1步中,调用SpringFactoriesLoader.loadFactoryNames(type, classLoader)
方法,
是用来获取指定接口的实现类的名称字符串,而后就可以根据名称创建实例了。
例如我们传递的参数是:ApplicationContextInitializer.class
,那么获取的就是ApplicationContextInitializer
下面的实现类的名称字符串集合。
那么这里是如何根据接口找到对应的实现类名称呢?
3)loadFactoryNames加载类名称
那么loadFactoryNames是如何根据接口找到对应的实现类名称呢,继续跟入:
SpringFactoriesLoader.loadFactoryNames(type, classLoader)
方法:
// SpringFactoriesLoader
/**
* 使用指定的类加载器,加载{@value #FACTORIES_RESOURCE_LOCATION}中记录的,指定factoryClass
* 类型的实现类的全路径名。
* @param factoryClass 需要加载的接口或抽象类
* @param classLoader 用来加载资源的类加载器
*/
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
// 获取接口名称
String factoryClassName = factoryClass.getName();
// 从loadSpringFactories(classLoader)方法返回的是一个Map:key是接口名称字符串,值是实现类的名称集合
// 然后就可以调用map的get方法,根据factoryClass名称获取对应的实现类名称数组
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
注意到这里是先调用loadSpringFactories(classLoader)
方法,
此方法方法返回的是一个Map:key是接口名称字符串,值是实现类的名称集合。
那么,loadSpringFactories
方法是如何读取到这样的map呢?上面有截图分析这一部分,代码如下:
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 尝试从缓存中获取结果
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 从默认路径加载资源文件,地址是:"META-INF/spring.factories"
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
// 创建空map
result = new LinkedMultiValueMap<>();
// 遍历资源路径
while (urls.hasMoreElements()) {
// 获取某个路径
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 加载文件内容,文件中是properties格式,key是接口名,value是实现类的名称以,隔开
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
// 获取key的 名称
String factoryClassName = ((String) entry.getKey()).trim();
// 将实现类字符串变成数组并遍历,然后添加到结果result中
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
// 缓存中放一份,下次再加载可以从缓存中读取
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
这个方法是利用ClassLoader
加载classpath下的所有的/META-INF/spring.factories
文件。
注意:所有jar包都会被扫描和查找。
例如,在spring-boot的jar包中,就有这样的文件
spring.factories内容类似这样:
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\\
org.springframework.boot.env.PropertiesPropertySourceLoader,\\
org.springframework.boot.env.YamlPropertySourceLoader
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\\
org.springframework.boot.context.event.EventPublishingRunListener
# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\\
org.springframework.boot.diagnostics.FailureAnalyzers
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\\
org.springframework.boot.ClearCachesApplicationListener,\\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\\
org.springframework.boot.context.FileEncodingApplicationListener,\\
以上是关于springBoot自动配置原理源码分析+自定义starter启动器+可视化监控+mybatisPlus使用的主要内容,如果未能解决你的问题,请参考以下文章
SpringBoot源码分析----SpringBoot自动配置
SpringBoot源码分析----SpringBoot自动配置原理