一、SpringBoot是什么
SpringBoot是依赖于Spring的,比起Spring,除了拥有Spring的全部功能以外,SpringBoot无需繁琐的xml配置,这取决于它自身强大的自动装配功能。
并且SpringBoot自身已嵌入Tomcat、Jetty等web容器,集成了SpringMVC,使得SpringBoot可以直接运行,不需要额外的容器。
SpringBoot提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标,健康检测、外部配置等。
其实Spring大家都知道,Boot是启动的意思。所以,SpringBoot其实就是一个启动Spring项目的一个工具而已,总而言之,SpringBoot 是一个服务于框架的框架。也可以说SpringBoot是一个工具,这个工具简化了Spring的配置。
二、Spring Boot的核心功能
-
可独立运行的Spring项目:Spring Boot可以以jar包的形式独立运行。
-
内嵌的Servlet容器:Spring Boot可以选择内嵌Tomcat、Jetty或者Undertow,无须以war包形式部署项目。
-
简化的Maven配置:Spring提供推荐的基础POM文件来简化Maven配置。
-
自动配置Spring:Spring Boot会根据项目依赖来自动配置Spring框架,极大地减少项目要使用的配置。
-
提供生产就绪型功能:提供可以直接在生产环境中使用的功能,如性能指标、应用信息和应用健康检查。
-
无代码生成和xml配置:Spring Boot不生成代码。完全不需要任何xml配置即可实现Spring的所有配置。
三、SpringBoot Project Demo
新建一个SpringBoot项目,mavan依赖包如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
SpringBoot Project Demo
四、SpringBoot启动过程
SpringBoot的启动经过了一些一系列的处理,我们先看看整体过程的流程图
SpringBoot启动过程org.springframework.boot.SpringApplication
源码:
public class SpringApplication {
// 运行 spring 应用程序
public ConfigurableApplicationContext run(String... args) {
// 记录启动时间
long startTime = System.nanoTime();
// spring应用上下文,也就是我们所说的spring根容器
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
//java.awt.headless是J2SE的一种模式用于在缺少显示屏、键盘或者鼠标时的系统配置,很多监控工具如jconsole 需要将该值设置为true,系统变量默认为true
configureHeadlessProperty();
//获取spring.factories中的监听器变量,args为指定的参数数组,默认为当前类SpringApplication
//第一步:获取并启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
// 触发ApplicationStartingEvent事件,启动监听器会被调用,一共5个监听器被调用,但只有两个监听器在此时做了事
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 参数封装,也就是在命令行下启动应用带的参数,如--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//第二步:构造容器环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 配置spring.beaninfo.ignore,并添加到名叫systemProperties的PropertySource中;默认为true即开启
configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = printBanner(environment);
//第三步:创建容器
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
//第四步:准备容器
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//第五步:刷新容器 :解析配置文件,加载业务 `bean`,启动 `tomcat` 等
refreshContext(context);
//第六步:刷新容器后的扩展接口
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
} catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
} catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
}
4.1 运行SpringApplication.run()
方法
可以肯定的是,所有的标准的SpringBoot的应用程序都是从run方法开始的
package com.alanchen.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringbootTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootTestApplication.class, args);
}
}
4.1.1 源码分析
进入run方法后,会 new 一个SpringApplication对象,创建这个对象的构造函数做了一些准备工作,编号第2~5步就是构造函数里面所做的事情。
SpringApplication源码4.1.2 SpringBoot的三种启动方式
4.1.2.1 @SpringBootApplication
(最常用方式)
@SpringBootApplication
注解的作用是标注这是一个SpringBoot的应用,被标注的类是一个主程序, SpringApplication.run(App.class, args);
传入的类App.class必须是被@SpringBootApplication
标注的类。
@SpringBootApplication
是一个组合注解,组合了其他相关的注解,点进去注解后我们可以看到,这个注解集成了@EnableAutoConfiguration
、@ComponentScan
。在这里的@ComponentScan()
注解有一堆东西,它的作用是将主配置类所在包及其下面所有后代包的所有注解扫描。
SpringBootApplication源码:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};
//其他代码省略
}
SpringBootApplication使用起来更加简单,只需要一个注解即可完成
package com.spring.controller;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App {
public static void main(String[] args) {
// 启动springboot
SpringApplication.run(App.class,args);
}
}
4.1.2.2 @EnableAutoConfiguration
@EnableAutoConfiguration
的作用是开启自动装配,帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot,并创建对应配置类的Bean,并把该Bean实体交给IOC容器进行管理。
@EnableAutoConfiguration
@RestController
public class IndexController {
// 访问路径 http://localhost:8080/index
@RequestMapping("/index")
public String index(){
System.out.println("我进来了");
return "index controller";
}
public static void main(String[] args) {
// 启动springboot
SpringApplication.run(IndexController.class,args);
}
}
4.1.2.3 @ComponentScan
@ComponentScan()
注解的作用是根据定义的扫描路径,将符合规则的类加载到spring容器中,比如在类中加入了以下注解 @Controller、@Service、@Mapper 、@Component、@Configuration 等等。
@EnableAutoConfiguration // 开启自动装配
@ComponentScan("com.spring.controller")
public class App {
public static void main(String[] args) {
// 启动springboot
SpringApplication.run(App.class,args);
}
}
4.2 确定应用程序类型
在SpringApplication的构造方法内,首先会通过 WebApplicationType.deduceFromClasspath();
方法判断当前应用程序的容器,默认使用的是Servlet 容器,除了servlet之外,还有NONE 和 REACTIVE (响应式编程)。
WebApplicationType.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;
}
这里主要是通过判断REACTIVE相关的字节码是否存在,如果不存在,则web环境即为SERVLET类型。这里设置好web环境类型,在后面会根据类型初始化对应环境。
4.3 加载所有的初始化器
这里加载的初始化器是SpringBoot自带初始化器,从META-INF/spring.factories
配置文件中加载的,那么这个文件在哪呢?自带有2个,分别在源码的jar包的 spring-boot-autoconfigure
项目和 spring-boot
项目里面各有一个
spring.factories
文件里面,看到开头是 org.springframework.context.ApplicationContextInitializer
接口就是初始化器了
当然,我们也可以自己实现一个自定义的初始化器:实现 ApplicationContextInitializer接口既可。
4.3.1 自定义的初始化器
实现ApplicationContextInitializer接口
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("我是初始化的 MyApplicationContextInitializer...");
}
}
在resources目录下添加META-INF/spring.factories
配置文件,内容如下,将自定义的初始化器注册进去。spring.factories配置文件:
org.springframework.context.ApplicationContextInitializer=\
com.alanchen.demo.MyApplicationContextInitializer
启动SpringBoot后,就可以看到控制台打印的内容了,在这里我们可以很直观的看到它的执行顺序,是在打印banner的后面执行的。
自定义的初始化器4.4 加载所有的监听器
加载监听器也是从META-INF/spring.factories
配置文件中加载的,与初始化不同的是,监听器加载的是实现了ApplicationListener 接口的类。
4.5 设置程序运行的主类
deduceMainApplicationClass();
这个方法仅仅是找到main方法所在的类,为后面的扫包作准备,deduce是推断的意思,所以准确地说,这个方法作用是推断出主方法所在的类。
deduceMainApplicationClass()
源码:
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
4.6 开启计时器
程序运行到这里,就已经进入了run方法的主体了,第一步调用的run方法是静态方法,那个时候还没实例化SpringApplication对象,现在调用的run方法是非静态的,是需要实例化后才可以调用的,进来后首先会开启计时器,这个计时器有什么作用呢?顾名思义就使用来计时的嘛,计算SpringBoot启动花了多长时间。关键代码如下:
new SpringApplication对象 计时器4.7 将java.awt.headless设置为true
这里将java.awt.headless
设置为true,表示运行在服务器端,在没有显示器器和鼠标键盘的模式下照样可以工作,模拟输入输出设备功能。
做了这样的操作后SpringBoot想干什么呢?其实是想设置该应用程序,即使没有检测到显示器,也允许其启动。对于服务器来说是不需要显示器的,所以要这样设置。
java.awt.headless headless private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
通过方法可以看到,setProperty()
方法里面又有个getProperty();
这不多此一举吗?其实getProperty()
方法里面有2个参数, 第一个key值,第二个是默认值,意思是通过key值查找属性值,如果属性值为空,则返回默认值 true,保证了一定有值的情况。
4.8 获取并启用监听器
这一步 通过监听器来实现初始化的的基本操作,这一步做了2件事情:
1、创建所有 Spring 运行监听器并发布应用启动事件。
2、启用监听器。
4.9 设置应用程序参数
将执行run方法时传入的参数封装成一个对象
设置应用程序参数仅仅是将参数封装成对象,对象的构造函数如下:
public class DefaultApplicationArguments implements ApplicationArguments {
private final Source source;
private final String[] args;
public DefaultApplicationArguments(String... args) {
Assert.notNull(args, "Args must not be null");
this.source = new Source(args);
this.args = args;
}
// 其他代码省略
}
那么问题来了,这个参数是从哪来的呢?其实就是main方法里面执行静态run方法传入的参数。
参数4.10 准备环境变量
准备环境变量,包含系统属性和用户配置的属性,执行的代码块在 prepareEnvironment方法内。
prepareEnvironment打了断点之后可以看到,它将maven和系统的环境变量都加载进来了。
加载变量
4.11 忽略bean信息
这个方法configureIgnoreBeanInfo()
这个方法是将spring.beaninfo.ignore
的默认值值设为true,意思是跳过beanInfo的搜索,其设置默认值的原理和第7步一样。
当然也可以在配置文件中添加以下配置来设为false
spring.beaninfo.ignore=false
4.12 打印banner信息
显而易见,这个流程就是用来打印控制台那个很大的Spring的banner的
banner信息那他在哪里打印的呢?他在SpringBootBanner.java 里面打印的,这个类实现了Banner 接口,而且banner信息是直接在代码里面写死的。
SpringBootBanner printBanner源码private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
: new DefaultResourceLoader(null);
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
4.12.1 Banner图在哪里加载
既然我们想要更换Spring启动的默认logo,首先我们就的知道,这logo是怎么出现的,只有搞明白了这个问题,我们才能去修改它。
其实Spring Boot启动打印默认logo的类是SpringApplicationBannerPrinter类,SpringBoot 默认寻找 Banner的顺序是:
- 首先依次在Classpath下找文件banner.gif,banner.jpg和 banner.png,使用优先找到的。
- 若没找到上面文件的话,继续Classpath下找banner.txt。
- 若上面都没有找到的话, 用默认的 SpringBootBanner,也就是上面输出的 SpringBoot logo。
4.12.2 自定义Banner图
一般是把banner.txt文件放在src/main/resources/
目录下。既然找到了关键的问题,我们就可以自己创建一个banner.txt文件,让他来覆盖SpringBoot默认的logo,实现我们自定义的logo。在resources目录下添加一个banner.txt 的文件即可,txt文件内容如下:
只需要加一个文件即可,其他什么都不用做,然后直接启动SpringBoot,就可以看到效果了。
4.12.3 第三方Banner生成工具
在项目中更改图像很简单,无非是添加一个banner.txt文件而已,但是文件的中图咱么搞啊,难道要自己手敲吗,这可不是一般人能搞的出来的啊。所以这里给大家介绍几个网站,可以生成一些图形。
4.12.3.1 Text toASCII Art Generator
字母转换为ASCII 艺术字,推荐 Text toASCII Art Generator ,优点:
-
它支持的字体效果(艺术字)最多。
-
并且可以通过点击 Test All 同时生成所有效果(共314种)来供你选择,而无需一个一个去选择,这样可以大大减少挑选时间。
-
还可以通过 More Opts 来设置以编程注释或回显输出的形式格式化输出。
Text toASCII Art Generator地址:http://patorjk.com/software/taag/
4.12.3.2 ASCII艺术字(图)集
Ascii艺术字,可以在这里寻找现成的一些图集(也可以生成ASCII艺术字),可以直接搜索你想要的图形,搜索出来的结果可以直接下载或者复制都可以(截图右上角)。
ascii-art地址:https://www.bootschool.net/ascii-art/search
4.13 创建应用程序的上下文
实例化应用程序的上下文, 调用createApplicationContext()
方法,这里就是用反射创建对象。
4.14 实例化异常报告器
异常报告器是用来捕捉全局异常使用的,当SpringBoot应用程序在发生异常时,异常报告器会将其捕捉并做相应处理,在spring.factories
文件里配置了默认的异常报告器。
需要注意的是,这个异常报告器只会捕获启动过程抛出的异常,如果是在启动完成后,在用户请求时报错,异常报告器不会捕获请求中出现的异常。
异常4.14.1 自定义异常报告器
MyExceptionReporter实现SpringBootExceptionReporter接口,并提供一个带参的构造函数
package com.alanchen.demo;
import org.springframework.boot.SpringBootExceptionReporter;
import org.springframework.context.ConfigurableApplicationContext;
public class MyExceptionReporter implements SpringBootExceptionReporter {
private ConfigurableApplicationContext context;
//必须要有一个有参的构造函数,否则自定义异常报告器无效
public MyExceptionReporter(ConfigurableApplicationContext context){
this.context = context;
}
@Override
public boolean reportException(Throwable failure) {
System.out.println("进入自定义异常报告器");
failure.printStackTrace();
//返回false会打印详细的springboot信息,返回true只打印异常信息
return false;
}
}
在spring.factories
文件中注册异常报告器
org.springframework.context.ApplicationContextInitializer=\
com.alanchen.demo.MyApplicationContextInitializer
org.springframework.boot.SpringBootExceptionReporter=\
com.alanchen.demo.MyExceptionReporter
spring.factories
接着我们在application.yml 中把端口号设置为一个很大的值,这样肯定会报错
设置端口 进入自定义异常报告器4.15 准备上下文环境
准备上下文环境4.15.1 实例化单例的beanName生成器
在postProcessApplicationContext(context);
方法里面。使用单例模式创建 了BeanNameGenerator对象,其实就是beanName生成器,用来生成bean对象的名称。
4.15.2 执行初始化方法
初始化方法有哪些呢?还记得第3步里面加载的初始化器嘛?其实是执行第3步加载出来的所有初始化器,实现了ApplicationContextInitializer接口的类。
4.15.3 将启动参数注册到容器中
这里将启动参数以单例的模式注册到容器中,是为了以后方便拿来使用,参数的beanName 为 :springApplicationArguments
4.16 刷新上下文
刷新上下文已经是spring的范畴了,自动装配和启动 tomcat就是在这个方法里面完成的。
刷新上下文4.17 刷新上下文后置处理
afterRefresh方法是启动后的一些处理,留给用户扩展使用,目前这个方法里面是空的。
afterRefresh4.18 结束计时器
到这一步,SpringBoot其实就已经完成了,计时器会打印启动SpringBoot的时长。
结束计时器4.19 发布上下文准备就绪事件
发布上下文准备就绪事件4.20 执行自定义的run方法
这是一个扩展功能,callRunners(context, applicationArguments)
可以在启动完成后执行自定义的run方法;有2中方式可以实现:
- 实现ApplicationRunner接口
- 实现CommandLineRunner接口
下来我们验证一把,为了方便代码可读性,我把这2种方式都放在同一个类里面
package com.alanchen.demo;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class MyRunner implements ApplicationRunner, CommandLineRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("自定义ApplicationRunner运行");
}
@Override
public void run(String... args) throws Exception {
System.out.println("自定义CommandLineRunner运行");
}
}
启动结果
网友评论