- SpringBoot入门到精通-Spring的注解编程(一)
- SpringBoot入门到精通-SpringBoot入门(二)
- SpringBoot入门到精通-Spring的基本使用(三)
- SpringBoot入门到精通-SpringBoot集成SSM(四)
- SpringBoot入门到精通-SpringBoot自动配置原理(五)
- SpringBoot入门到精通-SpringBoot自定义starter(六)
SpringApplication
类提供了一种可通过运行 main()
方法来启动 Spring 应用的简单方式。多数情况下,您只需要委托给静态的 SpringApplication.run
方法:
public static void main(String[] args) {
SpringApplication.run(MySpringConfiguration.class, args);
}
如果 SpringApplication
的默认设置不符合您的想法,您可以创建本地实例进行定制化。例如,要关闭 banner,您可以这样:
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MySpringConfiguration.class);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);
}
2.SpringApplication.run执行流程
SpringApplication可用于从 Java 主方法引导和启动 Spring 应用程序的类。默认情况下,类将执行以下步骤来启动应用:
-
创建一个适当的[
ApplicationContext
]实例(取决于您的类路径) -
注册 [
CommandLinePropertySource
]以将命令行参数公开为 Spring 属性 -
刷新应用程序上下文,加载所有单例 bean
-
触发任何[
CommandLineRunner
]bean
下面我们就来详细分析一下它的执行流程,见:org.springframework.boot.SpringApplication#run(java.lang.String…)
public ConfigurableApplicationContext run(String... args) {
//创建秒表,用来计算启动事件
StopWatch stopWatch = new StopWatch();
//启动秒表
stopWatch.start();
//Spring IOC 容器对象
ConfigurableApplicationContext context = null;
//收集Spring Boot 异常报告器的list
Collection exceptionReporters = new ArrayList();
//配置无头属性,java.awt.headless
configureHeadlessProperty();
//SpringBoot的SpringApplication run方法的侦听器 监听器,
//SpringApplicationRunListeners维护了一个 SpringApplicationRunListener 集合
SpringApplicationRunListeners listeners = getRunListeners(args);
//会触发所有 SpringApplicationRunListener#starting的执行
//,会通过SimpleApplicationEventMulticaster广播一个ApplicationStartingEvent事件
listeners.starting();
try {
//把应用参数封装到DefaultApplicationArguments,通过它可以访问应用参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//创建环境对象,Environment包括了property和profile
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//配置忽略 Bean 信息 ,spring.beaninfo.ignore
configureIgnoreBeanInfo(environment);
//打印横幅
Banner printedBanner = printBanner(environment);
//创建IOC容器对象 AnnotationConfigApplicationContext
context = createApplicationContext();
//创建Spring Boot 异常报告器实例。会扫描spring.factories下的 FailureAnalyzers实例,
//FailureAnalyzer是用于分析故障并提供可显示给用户的诊断信息
//比如:NoSuchBeanDefinitionFailureAnalyzer ; DataSourceBeanCreationFailureAnalyzer
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//刷新容器准备工作
//1.把environment绑定到context容器对象
//2.context后置处理,比如绑定resourceLoader
//3.触发 ApplicationContextInitializer#initialize初始化(用于在刷新之前初始化Context回调接口。)
//4.触发 listener.contextPrepared ,抛出 ApplicationContextInitializedEvent 事件
//5.把ApplicationArguments注册到容器中成为一个Bean
//6.把 Banner注册到容器中成为一个Bean
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新容器,底层走spring的刷新容器流程
refreshContext(context);
//空方法,留给我们扩展
afterRefresh(context, applicationArguments);
//暂定秒表
stopWatch.stop();
if (this.logStartupInfo) {
//打印秒表记录的时间
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//触发 SpringApplicationRunListener#started方法抛出 ApplicationStartedEvent 事件
listeners.started(context);
//调用 ApplicationRunner 和 CommandLineRunner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
//处理异常,会从exceptionReporters拿出异常进行打印
//以及会触发 SpringApplicationRunListeners#failed,广播 ApplicationFailedEvent事件
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
//执行listeners.running , 抛出 ApplicationReadyEvent 事件
listeners.running(context);
}
catch (Throwable ex) {
//处理异常,会从exceptionReporters拿出异常进行打印
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
//返回容器
return context;
}
画一个流程图总结一下
Spring体用的秒表,允许对多个任务进行计时,显示每个命名任务的总运行时间和运行时间。隐藏System.nanoTime()的使用,提高应用程序代码的可读性并减少计算错误的可能性。注意,此对象并非设计为线程安全的,也不使用同步。
public class StopWatch {
/**
* Identifier of this {@code StopWatch}.
* Handy when we have output from multiple stop watches and need to
* distinguish between them in log or console output.
*/
//任务的ID
private final String id;
private boolean keepTaskList = true;
//任务列表
private final List taskList = new LinkedList();
/** Start time of the current task. */
//开始时间
private long startTimeNanos;
/** Name of the current task. */
//当前任务名
@Nullable
private String currentTaskName;
@Nullable
private TaskInfo lastTaskInfo;
//任务数量
private int taskCount;
/** Total running time. */
//总时间
private long totalTimeNanos;
//开始任务,穿了一个“”作为taskName
public void start() throws IllegalStateException {
start("");
}
/**
* Start a named task.
*
The results are undefined if {@link #stop()} or timing methods are
* called without invoking this method first.
* @param taskName the name of the task to start
* @see #start()
* @see #stop()
*/
//开始任务
public void start(String taskName) throws IllegalStateException {
if (this.currentTaskName != null) {
throw new IllegalStateException("Can't start StopWatch: it's already running");
}
//任务名
this.currentTaskName = taskName;
//记录开始时间
this.startTimeNanos = System.nanoTime();
}
//停止秒表
public void stop() throws IllegalStateException {
if (this.currentTaskName == null) {
throw new IllegalStateException("Can't stop StopWatch: it's not running");
}
//时间差
long lastTime = System.nanoTime() - this.startTimeNanos;
//累计时间
this.totalTimeNanos += lastTime;
//创建一个TaskInfo任务信息
this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
if (this.keepTaskList) {
//加入任务列表
this.taskList.add(this.lastTaskInfo);
}
//增加任务数量
++this.taskCount;
//清空任务名
this.currentTaskName = null;
}
//以优雅的格式打印秒表记录的时间日志
public String prettyPrint() {
StringBuilder sb = new StringBuilder(shortSummary());
sb.append('\n');
if (!this.keepTaskList) {
sb.append("No task info kept");
}
else {
sb.append("---------------------------------------------\n");
sb.append("ns % Task name\n");
sb.append("---------------------------------------------\n");
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMinimumIntegerDigits(9);
nf.setGroupingUsed(false);
NumberFormat pf = NumberFormat.getPercentInstance();
pf.setMinimumIntegerDigits(3);
pf.setGroupingUsed(false);
for (TaskInfo task : getTaskInfo()) {
sb.append(nf.format(task.getTimeNanos())).append(" ");
sb.append(pf.format((double) task.getTimeNanos() / getTotalTimeNanos())).append(" ");
sb.append(task.getTaskName()).append("\n");
}
}
return sb.toString();
}
}
StopWatch秒表可以用来对多个任务计时,,start的时候会使用System.nanoTime()来获时间记录到startTimeNanos ,stop结束方计算时间差,然后会把每次的时间和任务名封装成TaskInfo,加入taskList。最后会累计每次任务的时间总额。提供了prettyPrint方法以优雅的格式组织秒表记录的时间日志。
但是要注意:虽然它可以允许多个任务记时,但是它并不是线程安全的。
4.SpringBootExceptionReporter异常报告 4.1.核心类认识SpringBootExceptionReporter是用于支持自定义上报SpringApplication启动错误的回调接口,它可以把启动的错误日志汇报给用户
@FunctionalInterface
public interface SpringBootExceptionReporter {
/**
* Report a startup failure to the user.
* @param failure the source failure
* @return {@code true} if the failure was reported or {@code false} if default
* reporting should occur.
*/
boolean reportException(Throwable failure);
}
reportException方法的作用就是为用户报告错误。它的唯一实现类是 FailureAnalyzers ,它提供了
final class FailureAnalyzers implements SpringBootExceptionReporter {
private static final Log logger = LogFactory.getLog(FailureAnalyzers.class);
private final ClassLoader classLoader;
//故障分析仪
private final List analyzers;
//报告指定的异常
@Override
public boolean reportException(Throwable failure) {
//把异常封装到FailureAnalysis
//FailureAnalysis中维护了很多的FailureAnalyzer,它的作用是分析故障并提供可显示给用户的诊断信息
FailureAnalysis analysis = analyze(failure, this.analyzers);
return report(analysis, this.classLoader);
}
//分析异常
private FailureAnalysis analyze(Throwable failure, List analyzers) {
for (FailureAnalyzer analyzer : analyzers) {
try {
//把Throwable异常信息封装成FailureAnalysis
FailureAnalysis analysis = analyzer.analyze(failure);
if (analysis != null) {
return analysis;
}
}
catch (Throwable ex) {
logger.debug(LogMessage.format("FailureAnalyzer %s failed", analyzer), ex);
}
}
return null;
}
private boolean report(FailureAnalysis analysis, ClassLoader classLoader) {
//加载FailureAnalysisReporter, FailureAnalysisReporter用来 向用户报告FailureAnalysis分析。
List reporters = SpringFactoriesLoader.loadFactories(FailureAnalysisReporter.class,
classLoader);
if (analysis == null || reporters.isEmpty()) {
return false;
}
for (FailureAnalysisReporter reporter : reporters) {
//报告异常
reporter.report(analysis);
}
return true;
}
reportException方法接收一个Throwable ,然后Throwable 会被封装到FailureAnalysis。然后通过SpringFactoriesLoader去加载FailureAnalysisReporter(向用户报告FailureAnalysis分析),通过FailureAnalysisReporter去报告异常。FailureAnalysis结构如下
public class FailureAnalysis {
//异常描述
private final String description;
private final String action;
//异常对象
private final Throwable cause;
LoggingFailureAnalysisReporter结构如下见:org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter#report
public final class LoggingFailureAnalysisReporter implements FailureAnalysisReporter {
private static final Log logger = LogFactory.getLog(LoggingFailureAnalysisReporter.class);
@Override
public void report(FailureAnalysis failureAnalysis) {
if (logger.isDebugEnabled()) {
logger.debug("Application failed to start due to an exception", failureAnalysis.getCause());
}
//把错误日志打印到控制台
if (logger.isErrorEnabled()) {
logger.error(buildMessage(failureAnalysis));
}
}
//构建错误日志内容
private String buildMessage(FailureAnalysis failureAnalysis) {
StringBuilder builder = new StringBuilder();
builder.append(String.format("%n%n"));
builder.append(String.format("***************************%n"));
builder.append(String.format("APPLICATION FAILED TO START%n"));
builder.append(String.format("***************************%n%n"));
builder.append(String.format("Description:%n%n"));
builder.append(String.format("%s%n", failureAnalysis.getDescription()));
if (StringUtils.hasText(failureAnalysis.getAction())) {
builder.append(String.format("%nAction:%n%n"));
builder.append(String.format("%s%n", failureAnalysis.getAction()));
}
return builder.toString();
}
4.2.报告异常
在SpringApplication#run方法中有try-catch操作,如果启动出现异常,会执行org.springframework.boot.SpringApplication#handleRunFailure来处理异常
private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception,
Collection exceptionReporters, SpringApplicationRunListeners listeners) {
try {
try {
//处理退出码,发布一个ExitCodeEvent事件
handleExitCode(context, exception);
if (listeners != null) {
//发布ApplicationFailedEvent事件
listeners.failed(context, exception);
}
}
finally {
//报告异常,通过LoggingFailureAnalysisReporter 把异常打印到控制台
reportFailure(exceptionReporters, exception);
if (context != null) {
context.close();
}
}
}
catch (Exception ex) {
logger.warn("Unable to close ApplicationContext", ex);
}
ReflectionUtils.rethrowRuntimeException(exception);
}
上面重要是发布ApplicationFailedEvent事件, 然后通过SpringBootExceptionReporter#reportException去把异常打印到控制台,
5.监听器机制上面代码中有很多地方都出现了事件发布,比如: SpringApplicationRunListeners listeners = getRunListeners(args)
它的作用是广播ApplicationStartingEvent事件,这用到了Spring的监听器机制。我们可以认为以 Listenner 结尾的类都是监听器,监听器使用到了观察者设计模式,其作用是监听一些事件的发生从而进行一些操作。监听器的好处是可以实现代码解耦,对此你可能不是很能理解,我这里用一个js例子来代理理解事件机制
function dothing(){
//回调函数
}
//监听button的click事件
$("#button").click(dothing);
上面代码相信你是写过的,就是一个JS监听按钮点击事件,这里需要明确三个角色
- button : 事件源,这个事件发生在谁身上
- click : 事件类型 ,按钮发生了什么事件
- dothing : 回调函数,当button被点击,触发 dothing函数。
那么Java中的事件机制和上面案例很相似,我这里有个案例:当用户注册成功,给用户推送一条短信,使用事件机制来实现
这么理解这幅图
- 首先需要定义一个事件类型RegisterApplicationEvent 继承于ApplicationEvent , 代表的注册这个事件,好比是"click"
- 然后需要在注册逻辑中,使用事件发布器ApplicationEventPublisher 发布该事件 ,好比 button 被 click了
- 事件被发布,需要触发某段逻辑,所以要写一个监听器类实现ApplicationListernner,该监听器监听的是“注册事件”。
- 然后就调用短信发送逻辑发送短信即可。好比是上面的dothing回调函数。
相信大致的流程你是看懂了,但是有些陌生类让我们比较迷惑,下面我们就来系统的认识一下这些类。
5.1. 核心类认识EventListener
EventListener是java提供的最顶层的监听器接口,不管是Servlet的监听器还是Spring的监听器都是该接口的子类(所有事件侦听器接口都必须实现于接口)。
/**
* A tagging interface that all event listener interfaces must extend.
* @since JDK1.1
*/
public interface EventListener {
}
ApplicationListener
Spring提供的基于 Observer 设计模式(观察者)设计的监听器接口,实现了EventListener。
@FunctionalInterface
public interface ApplicationListener extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
编写Spring的监听器都需要实现 ApplicationListener这个接口 , 比如这个类
public class ScheduledAnnotationBeanPostProcessor implements ApplicationListener...省略{
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext() == this.applicationContext) {
// Running in an ApplicationContext -> register tasks this late...
// giving other ContextRefreshedEvent listeners a chance to perform
// their work at the same time (e.g. Spring Batch's job registration).
finishRegistration();
}
}
}
ScheduledAnnotationBeanPostProcessor就Schedule定时任务的处理器 ,它是实现ApplicationListener监听器接口,监听了ContextRefreshedEvent容器刷新完成事件,也就是说当Spring容器刷新完成,就会触发 onApplicationEvent方法,那么该监听器就可以对 Scheduled做一些处理了。
ApplicationEvent
Spring提供的事件对象,实现于EventObject,它代表了事件的类型。下面是EventObject的结构
public class EventObject implements java.io.Serializable {
private static final long serialVersionUID = 5516075349620653480L;
/**
* The object on which the Event initially occurred.
*/
protected transient Object source;
/**
* Constructs a prototypical Event.
*
* @param source The object on which the Event initially occurred.
* @exception IllegalArgumentException if source is null.
*/
public EventObject(Object source) {
if (source == null)
throw new IllegalArgumentException("null source");
this.source = source;
}
/**
* The object on which the Event initially occurred.
*
* @return The object on which the Event initially occurred.
*/
public Object getSource() {
return source;
}
/**
* Returns a String representation of this EventObject.
*
* @return A a String representation of this EventObject.
*/
public String toString() {
return getClass().getName() + "[source=" + source + "]";
}
}
Object source; 可以用来传送数据 ,当我们要发布一个事件的时候,就需要创建一个事件对象,同时事件对象指定一个shource。在Spring中内置了很多事件对象,他们都是ApplicationEvent的子类比如:
- ContextStartedEvent : ApplicationContext Spring容器启动时引发的事件。
- ContextRefreshedEvent : 初始化或刷新ApplicationContext时引发的事件。
- ContextStoppedEvent:当ApplicationContext停止时引发的事件。
- ContextClosedEvent:ApplicationContext关闭时引发的事件。
下面这几个是SpringBoot提供的事件对象,也是ApplicationEvent的子类,在上面的SpringBoot执行流程中也有看到
- ApplicationPreparedEvent : 当SpringApplication启动并且ApplicationContext已完全准备好但未刷新时发布的事件。 将加载 bean 定义,并且在此阶段Environment已准备好使用
- ApplicationStartingEvent : 在SpringApplication启动后尽可能早地发布事件 - 在Environment或ApplicationContext可用之前,但在ApplicationListener已注册之后
- ApplicationStartedEvent :刷新应用程序上下文但在调用任何application和command line运行程序之前发布的事件。
- ApplicationReadyEvent :发布该事件表明应用程序已准备好为请求提供服务
- ApplicationFailedEvent : SpringApplication启动失败时发布的事件
ApplicationEventPublisher
事件发布器,提供了发布事件的基础功能
@FunctionalInterface
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
void publishEvent(Object event);
}
大名鼎鼎的 ApplicationContext 容器对象就是实现该接口ApplicationEventPublisher ,拥有了事件发布的能力。
他们的继承体系如下
我们来把上面的案例实现以下,即:当注册成功,给用户发送一条短信,确定好三个角色
- 事件是什么?“注册成功事件” -> 需要定义一个事件对象
- 事件源是什么?“注册逻辑” -> 在注册成功后,发布"注册事件"
- 做出的反映是什么?“推送短信” -> 监听“注册事件”,推送短信
第一步,创建一个事件对象,继承ApplicationEvent
//注册成功事件对象
public class RegisterSuccessEvent extends ApplicationEvent {
//Object source :待会要传递的参数
public RegisterSuccessEvent(Object source) {
super(source);
}
}
第二步,创建事件监听器,监听RegisterSuccessEvent事件
//注册成功的监听器,监听 RegisterSuccessEvent事件
@Slf4j
@Service
public class RegisterSuccessListenner implements ApplicationListener {
@Override
public void onApplicationEvent(RegisterSuccessEvent event) {
Map source = (Map) event.getSource();
if(source == null){
log.error("短信内容不能为空");
}else{
String phone = source.get("phone");
String message = source.get("message");
log.info("注册成功,给: {} 发送短信,内容: {}" , phone , message);
}
}
}
第三步,注册逻辑,抛出RegisterSuccessEvent事件,我这里就简单模拟一下
//注入事件发布器
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@RequestMapping("/register")
public JSONResult register(){
Map map = new HashMap();
map.put("phone","18233333333");
map.put("message","注册成功");
applicationEventPublisher.publishEvent(new RegisterSuccessEvent(map));
return JSONResult.builder().build();
}
第四步,启动测试,效果如下
2022-01-14 10:10:31.941 INFO 20448 — [nio-8081-exec-5] cn.whale.event.RegisterSuccessListenner : 注册成功,给: 18233333333 发送短信,内容: 注册成功
这里我再给一个小案例:项目启动完成,往数据库初始化一个管理员账号,这个应该怎么做呢?
我们可以编写一个类,实现 ApplicationListener
,监听事件是Spring容器刷新完成,然后复写onApplicationEvent
方法,在该方法中往数据库保存管理员账号即可。你可以自己试着写一下。
这里给大家介绍web环境中,三个比较有用的监听器
HttpSessionListener : 它是针对于Session的监听器,提供了两个方法来监听session的创建和销毁,代码如下
public interface HttpSessionListener extends EventListener {
//session创建
public default void sessionCreated(HttpSessionEvent se) {
}
//session销毁
public default void sessionDestroyed(HttpSessionEvent se) {
}
}
如果我们实现该接口,就可以监听到session的什么周期方法了,你可能会问,这有什么用,那我给一个需求你思考一下怎么做:“记录用户在网站的登录到退出的时间”
ServletContextListener : 它是用来监听ServletContext上下文对象的生命周期,结构如下
public interface ServletContextListener extends EventListener {
//上下文初始化,代表应用启动
public default void contextInitialized(ServletContextEvent sce) {
}
//上下文销毁,代表应用结束
public default void contextDestroyed(ServletContextEvent sce) {
}
}
我这么说把,Spirng整合web,就是通过实现ServletContextListenner监听器 ,复写 contextInitialized方法,然后在该方法中创建WebApplicationContext容器。所以该监听器可以监听整个程序的启动和销毁。
ServletRequestListener : 它是监听request对象的生命周期方法
public interface ServletRequestListener extends EventListener {
//销毁
public default void requestDestroyed (ServletRequestEvent sre) {
}
//request初始化
public default void requestInitialized (ServletRequestEvent sre) {
}
}
该监听器是监听request对象的初始化和销毁 ,我不知道你有没有通过 RequestContextHolder.getRequestAttributes() 来获取Request对象,它的实现原理就是通过一个 RequestContextListener类去实现 ServletRequestListener 接口,然后在requestInitialized方法中拿到request对象,封装到RequestContextHolder中的一个 ThreadLocal requestAttributesHolder
中存储起来,所以我们在业务代码中可以通过RequestContextHolder直接获取Request对象。
讲完Spirng事件我们来讲SpringBoot中的事件,在SpringBoot启动流程中出现 SpringApplicationRunListeners#starting 等代码,其实就是在发布SpringBoot的事件。我们来看
4.1.SpringApplicationRunListenersSpringApplicationRunListeners : 下面是 SpringApplicationRunListeners的代码结构
class SpringApplicationRunListeners {
private final Log log;
//保存了很多的SpringApplicationRunListener
private final List listeners;
SpringApplicationRunListeners(Log log, Collection listeners) {
this.log = log;
this.listeners = new ArrayList(listeners);
}
//调用starting,本质是调用SpringApplicationRunListener的starting
void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting();
}
}
//调用environmentPrepared环境准备完成,本质是调用SpringApplicationRunListener的environmentPrepared
void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
//调用contextPrepared 容器刷新完成,本质是调用SpringApplicationRunListener的contextPrepared
void contextPrepared(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.contextPrepared(context);
}
}
//容器加载完成
void contextLoaded(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.contextLoaded(context);
}
}
//启动
void started(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.started(context);
}
}
//运行中
void running(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.running(context);
}
}
//出现错误
void failed(ConfigurableApplicationContext context, Throwable exception) {
for (SpringApplicationRunListener listener : this.listeners) {
callFailedListener(listener, context, exception);
}
}
//出现错误
private void callFailedListener(SpringApplicationRunListener listener, ConfigurableApplicationContext context,
Throwable exception) {
try {
listener.failed(context, exception);
}
catch (Throwable ex) {
if (exception == null) {
ReflectionUtils.rethrowRuntimeException(ex);
}
if (this.log.isDebugEnabled()) {
this.log.error("Error handling failed", ex);
}
else {
String message = ex.getMessage();
message = (message != null) ? message : "no error message";
this.log.warn("Error handling failed (" + message + ")");
}
}
}
}
在 SpringApplicationRunListeners这类中维护了一个 List listeners;
集合,SpringApplicationRunListeners#starting方法本质是调用的SpringApplicationRunListener#starting
方法
SpringApplicationRunListener是一个接口,其中维护了很多的事件方法
public interface SpringApplicationRunListener {
//开始
default void starting() {
}
//环境准备完成
default void environmentPrepared(ConfigurableEnvironment environment) {
}
//容器准备完成
default void contextPrepared(ConfigurableApplicationContext context) {
}
//容器加载完成
default void contextLoaded(ConfigurableApplicationContext context) {
}
//启动成功
default void started(ConfigurableApplicationContext context) {
}
//运行中
default void running(ConfigurableApplicationContext context) {
}
//启动失败
default void failed(ConfigurableApplicationContext context, Throwable exception) {
}
}
这里我找到了它的唯一一个默认实现类EventPublishingRunListener , 看名字是一个时间发布器
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
...省略...
//简单应用事件广播器
private final SimpleApplicationEventMulticaster initialMulticaster;
@Override
public void starting() {
//通过 SimpleApplicationEventMulticaster 发布ApplicationStartingEvent 应用开始启动事件
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
//通过 SimpleApplicationEventMulticaster 发布ApplicationEnvironmentPreparedEvent 应用环境准备完成事件
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
//通过 SimpleApplicationEventMulticaster 发布ApplicationContextInitializedEvent 应用初始化完成事件
this.initialMulticaster
.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
for (ApplicationListener listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
context.addApplicationListener(listener);
}
//通过 SimpleApplicationEventMulticaster 发布ApplicationPreparedEvent 应用准备成功事件
this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}
@Override
public void started(ConfigurableApplicationContext context) {
//通过 ConfigurableApplicationContext 发布ApplicationStartedEvent 应用启动成功事件
context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
}
@Override
public void running(ConfigurableApplicationContext context) {
//通过 ConfigurableApplicationContext 发布ApplicationReadyEvent 应用启动完毕,可以接受请求了
context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
//应用启动失败的事件
ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
if (context != null && context.isActive()) {
// Listeners have been registered to the application context so we should
// use it at this point if we can
context.publishEvent(event);
}
else {
// An inactive context may not have a multicaster so we use our multicaster to
// call all of the context's listeners instead
if (context instanceof AbstractApplicationContext) {
for (ApplicationListener listener : ((AbstractApplicationContext) context)
.getApplicationListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
//处理错误
this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
//通过 initialMulticaster 发布ApplicationFailedEvent 应用启动失败
this.initialMulticaster.multicastEvent(event);
}
}
private static class LoggingErrorHandler implements ErrorHandler {
private static final Log logger = LogFactory.getLog(EventPublishingRunListener.class);
//打印错误
@Override
public void handleError(Throwable throwable) {
logger.warn("Error calling ApplicationEventListener", throwable);
}
}
}
在EventPublishingRunListener中维护了一个 SimpleApplicationEventMulticaster 事件广播器,其中 starting ;environmentPrepared ;contextPrepared;contextLoaded ;failed 使用的是SpringBoot提供的SimpleApplicationEventMulticaster去发布事件。而对于;started ;failed; running ;会用到 ConfigurableApplicationContext容器对象去发布事件,ConfigurableApplicationContext是ApplicationEventPublisher Spring的事件发布器的实现类。
下面这几个是SpringBoot提供的事件对象,也是ApplicationEvent的子类,在上面的SpringBoot执行流程中也有看到
- ApplicationPreparedEvent : 当SpringApplication启动并且ApplicationContext已完全准备好但未刷新时发布的事件。 将加载 bean 定义,并且在此阶段Environment已准备好使用
- ApplicationStartingEvent : 在SpringApplication启动后尽可能早地发布事件 - 在Environment或ApplicationContext可用之前,但在ApplicationListener已注册之后
- ApplicationStartedEvent :刷新应用程序上下文但在调用任何application和command line运行程序之前发布的事件。
- ApplicationReadyEvent :发布该事件表明应用程序已准备好为请求提供服务
- ApplicationFailedEvent : SpringApplication启动失败时发布的事件
- ApplicationEnvironmentPreparedEvent:当SpringApplication启动并且Environment首次可供检查和修改时发布的事件。
- ApplicationContextInitializedEvent :当SpringApplication启动并准备ApplicationContext并且 ApplicationContextInitializers 已被调用但在加载任何 bean 定义之前发布的事件
我们来看一下 initialMulticaster 是怎么广播事件的,如: this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
,见:org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
...省略...
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
}
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
//这里是拿到监听了该event事件的Listenner,循环去调用监听器
for (ApplicationListener listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
//调用监听器
invokeListener(listener, event);
}
}
}
protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
//错误处理器
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
//调用监听器
doInvokeListener(listener, event);
}
catch (Throwable err) {
//错误处理
errorHandler.handleError(err);
}
}
else {
doInvokeListener(listener, event);
}
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
//触发了 ApplicationListener#onApplicationEvent
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
...省略...
}
}
}
你应该看明白了 ,SimpleApplicationEventMulticaster 中先是根据某个事件类型比如 ApplicationStartingEvent ,获取到监听了该事件的ApplicationListener 监听类,循环去调用监听类即:调用ApplicationListener#onApplicationEvent方法去触发事件。
那么如果你的Listenner监听了ApplicationStartingEvent 该事件,你的onApplicationEvent方法就会被触发执行。
那我们再看一下ConfigurableApplicationContext#publishEvent是怎么发布事件的,如:context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
,见:org.springframework.context.support.AbstractApplicationContext#publishEvent
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
// Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType();
}
}
// Multicast right now if possible - or lazily once the multicaster is initialized
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
//这里还是通过 Multicaster 去广播事件
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
我们看到 ConfigurableApplicationContext 其实掉用了AbstractApplicationContext#publishEvent方法去广播事件,底层还是使用了 org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent去广播事件
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
SpringBoot的事件就讲到这,那么我们学会了什么呢?至少你应该知道如果我要在SpirngBoot启动,或者启动失败做一些自己的业务,那你应该怎么做了。
7.printBanner打印横幅横幅是SpringBoot启动时候在控制台打印的SpringBoot图案
我们可以在resources中创建banner.txt来自定义横幅内容 , 也可以通过 spring.main.banner-mode=off来关闭打印。
7.1.相关类介绍在SpringBoot中提供了一个Banner接口专门用于以编程方式编写横幅的接口类。
@FunctionalInterface
public interface Banner {
//打印横幅
void printBanner(Environment environment, Class sourceClass, PrintStream out);
//模式
enum Mode {
OFF,
CONSOLE,
LOG
}
}
我们来介绍一下这几个实现类
SpringBootBanner : SpringBoot默认的横幅打印实现类
class SpringBootBanner implements Banner {
//默认要打印的横幅
private static final String[] BANNER = { "", " . ____ _ __ _ _",
" /\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\", "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",
" \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )", " ' |____| .__|_| |_|_| |_\\__, | / / / /",
" =========|_|==============|___/=/_/_/_/" };
private static final String SPRING_BOOT = " :: Spring Boot :: ";
private static final int STRAP_LINE_SIZE = 42;
@Override
public void printBanner(Environment environment, Class sourceClass, PrintStream printStream) {
//打印横幅
for (String line : BANNER) {
printStream.println(line);
}
//拿到SpringBoot的版本号
String version = SpringBootVersion.getVersion();
version = (version != null) ? " (v" + version + ")" : "";
StringBuilder padding = new StringBuilder();
while (padding.length()
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?