SpringBoot自动配置和自定义starter
1. SpringBoot做了什么?
可以看到我们利用SpringBoot搭建好工程,开始专注业务开发,省去了所有的配置、依赖管理,非常简单!
那么这些省去的配置、依赖SpringBoot是什么时候帮我们完成的呢?
1.1 依赖管理
首先来看SpringBoot如何帮我们管理依赖及其版本信息。
打开项目的pom.xml文件,可以看到我们的项目依赖比较简单:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.itcast</groupId>
<artifactId>bank</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>bank</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<skipTests>true</skipTests>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
这里包含了4部分内容,分别是:
- 父工程配置
- Properties配置
- 依赖项
- 插件
1)父工程配置
这个项目有一个父工程配置:
可以看到使用的SpringBoot版本是最新的2.1.6RELEASE 版本。跟入这个pom查看,发现又继承了另一个父工程:
继续跟入,发现里面已经管理了各种依赖及其版本了,列举一部分大家看:
因此,我们的项目需要引入依赖时,只需要指定坐标,版本都有SpringBoot管理,避免了依赖间的冲突。
2)Properties:
properties中主要定义了项目的JDK版本:
3)依赖项:
这里我们总共引入了3个依赖项:
但是,看看项目中实际引入了哪些jar包:
当我们移除两个spring-boot-starter命名的依赖项时,可以发现所有的jar都消失了:
也就是说,在下面的这个依赖项(starter)中:
<!--web工程起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
定义好了一个web工程所需的所有依赖,当我们引入依赖时,无需像以前那样一个一个寻找需要的依赖坐标并且验证版本是否冲突,只需要引入这个starter即可!
4)插件:
最后是一个插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
这个是SpringBoot工程在打包时用到的插件。
5)总结:
因为SpringBoot对于依赖的管理,我们搭建项目时只需要引入starter,无需再依赖引入上话费更多时间,大大提高了开发效率。
1.2 xml配置去哪儿了?
在以前,我们搭建一个web工程,至少需要下列配置文件:
- tomcat配置:web.xml(包括spring监听器、SpringMVC的DispatcherServlet、各种过滤器)
- SpringMVC配置:springmvc.xml(包括注解驱动、视图解析器等)
- Spring配置:applicationContext.xml(包括数据源、Bean扫描、事务等)
- mybatis配置:mybatis-config.xml全局配置,mybatis与spring整合配置、mapper映射文件配置
完成所有配置需要花费大量时间,每天都淹没在xml的海洋中,整个人生都陷入了被xml支配的恐惧、黑暗当中。
直到有一天,SpringBoot如同一页方舟,将你从xml海洋中拯救出来。
在SpringBoot中,就用Java代码配置代替了以前繁杂的xml配置,而且这些配置都已经放到了SpringBoot提供的jar包中,因此我们引入starter依赖的那一刻,这些配置都已经生效了!这就是springBoot的自动配置功能。
SpringBoot的自动配置原理
1.3 SpringBoot自动配置初探
回到开始的问题,SpringBoot取消了xml,并且完成了框架的自动配置,那么是如何实现的?
其实在SpringBoot的中,已经提前利用Java配置的方式,为Spring平台及第三方库做好了默认配置,并打包到了依赖中。当我们引入SpringBoot提供的starter依赖时,就获得了这些默认配置
,因此我们就无需配置,开箱即用了!
默认配置的位置如图:
其中就包括了SpringMVC的配置:
并且在这些类中,定义了大量的Bean,包括以前我们自己需要配置的如:ViewResolver、HandlerMapping、HandlerAdapter等。
因此,我们就无需配置了。
至于,这些配置是如何被加载的?怎样生效的,我们进行源码分析。
2.源码分析
接下来我们来看看SpringBoot的源码,分析下SpringBoot的自动配置原理。
2.1.启动类
整个项目 的入口是带有main函数的启动类:BankApplication
package cn.itcast.bank;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BankApplication {
public static void main(String[] args) {
SpringApplication.run(BankApplication.class, args);
}
}
这里跟SpringBoot有关联的部分有两个,一个是SpringApplication.run(BankApplication.class, args);
,另一个就是启动类上的注解:@SpringBootApplication
。
我们分别来跟踪这两部分源码。
2.2.SpringApplication类的初始化和run
看下整个类的介绍:
可以看出,核心作用就是从主函数加载一个Spring的应用。
其中启动应用的是几个run方法:
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified source using default settings.
* @param primarySource the primary source to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] {
primarySource }, args);
}
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings and user supplied arguments.
* @param primarySources the primary sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
最终走的是第二个run方法。而这个方法做两件事情:
new SpringApplication(primarySources)
:创建本类实例run(args)
:运行Spring应用
2.2.1.构造函数
我把跟构造函数有关的几个变量和方法提取出来,方便查看:
// SpringApplication.java
/**
* 资源加载器
*/
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) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 将传入的启动类装入集合
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 判断当前项目的类型,可以是SERVLET、REACTIVE、NONE
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 初始化 initializers 数组
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 初始化 listeners 数组
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
注解:
ResourceLoader resourceLoader
:Spring中用来加载资源(配置文件)的加载器Class<?>... primarySources
:这里是Confi