定义自己的starter

  • SpringBoot入门到精通-Spring的注解编程(一)
  • SpringBoot入门到精通-SpringBoot入门(二)
  • SpringBoot入门到精通-Spring的基本使用(三)
  • SpringBoot入门到精通-SpringBoot集成SSM(四)
  • SpringBoot入门到精通-SpringBoot自动配置原理(五)
  • SpringBoot入门到精通-SpringBoot自定义starter(六)

1.认识SpringApplication

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 应用程序的类。默认情况下,类将执行以下步骤来启动应用:

  1. 创建一个适当的[ApplicationContext]实例(取决于您的类路径)

  2. 注册 [CommandLinePropertySource]以将命令行参数公开为 Spring 属性

  3. 刷新应用程序上下文,加载所有单例 bean

  4. 触发任何[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 异常报告器的listCollection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();//配置无头属性,java.awt.headlessconfigureHeadlessProperty();//SpringBoot的SpringApplication run方法的侦听器 监听器,//SpringApplicationRunListeners维护了一个 SpringApplicationRunListener 集合SpringApplicationRunListeners listeners = getRunListeners(args);//会触发所有 SpringApplicationRunListener#starting的执行//,会通过SimpleApplicationEventMulticaster广播一个ApplicationStartingEvent事件listeners.starting();try {//把应用参数封装到DefaultApplicationArguments,通过它可以访问应用参数ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//创建环境对象,Environment包括了property和profileConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);//配置忽略 Bean 信息 ,spring.beaninfo.ignoreconfigureIgnoreBeanInfo(environment);//打印横幅Banner printedBanner = printBanner(environment);//创建IOC容器对象 AnnotationConfigApplicationContextcontext = createApplicationContext();//创建Spring Boot 异常报告器实例。会扫描spring.factories下的 FailureAnalyzers实例,//FailureAnalyzer是用于分析故障并提供可显示给用户的诊断信息//比如:NoSuchBeanDefinitionFailureAnalyzer ; DataSourceBeanCreationFailureAnalyzerexceptionReporters = 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注册到容器中成为一个BeanprepareContext(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 和 CommandLineRunnercallRunners(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;}

画一个流程图总结一下

3.StopWatch秒表

Spring体用的秒表,允许对多个任务进行计时,显示每个命名任务的总运行时间和运行时间。隐藏System.nanoTime()的使用,提高应用程序代码的可读性并减少计算错误的可能性。注意,此对象并非设计为线程安全的,也不使用同步。

public class StopWatch {/*** Identifier of this {@code StopWatch}.* <p>Handy when we have output from multiple stop watches and need to* distinguish between them in log or console output.*///任务的IDprivate final String id;private boolean keepTaskList = true;//任务列表private final List<TaskInfo> taskList = new LinkedList<>();/** Start time of the current task. *///开始时间private long startTimeNanos;/** Name of the current task. *///当前任务名@Nullableprivate String currentTaskName;@Nullableprivate TaskInfo lastTaskInfo;//任务数量private int taskCount;/** Total running time. *///总时间private long totalTimeNanos;//开始任务,穿了一个“”作为taskNamepublic void start() throws IllegalStateException {start("");}/*** Start a named task.* <p>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<FailureAnalyzer> analyzers;//报告指定的异常@Overridepublic boolean reportException(Throwable failure) {//把异常封装到FailureAnalysis//FailureAnalysis中维护了很多的FailureAnalyzer,它的作用是分析故障并提供可显示给用户的诊断信息FailureAnalysis analysis = analyze(failure, this.analyzers);return report(analysis, this.classLoader);}//分析异常private FailureAnalysis analyze(Throwable failure, List<FailureAnalyzer> analyzers) {for (FailureAnalyzer analyzer : analyzers) {try {//把Throwable异常信息封装成FailureAnalysisFailureAnalysis 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<FailureAnalysisReporter> 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);@Overridepublic 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<SpringBootExceptionReporter> 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中的事件机制和上面案例很相似,我这里有个案例:当用户注册成功,给用户推送一条短信,使用事件机制来实现

这么理解这幅图

  1. 首先需要定义一个事件类型RegisterApplicationEvent 继承于ApplicationEvent , 代表的注册这个事件,好比是"click"
  2. 然后需要在注册逻辑中,使用事件发布器ApplicationEventPublisher 发布该事件 ,好比 button 被 click了
  3. 事件被发布,需要触发某段逻辑,所以要写一个监听器类实现ApplicationListernner,该监听器监听的是“注册事件”。
  4. 然后就调用短信发送逻辑发送短信即可。好比是上面的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<E extends ApplicationEvent> extends EventListener {/*** Handle an application event.* @param event the event to respond to*/void onApplicationEvent(E event);}

编写Spring的监听器都需要实现 ApplicationListener这个接口 , 比如这个类

public class ScheduledAnnotationBeanPostProcessor  implements ApplicationListener<ContextRefreshedEvent>...省略{@Overridepublic 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 ,拥有了事件发布的能力。

他们的继承体系如下

5.2.发布自定义事件

我们来把上面的案例实现以下,即:当注册成功,给用户发送一条短信,确定好三个角色

  1. 事件是什么?“注册成功事件” -> 需要定义一个事件对象
  2. 事件源是什么?“注册逻辑” -> 在注册成功后,发布"注册事件"
  3. 做出的反映是什么?“推送短信” -> 监听“注册事件”,推送短信

第一步,创建一个事件对象,继承ApplicationEvent

//注册成功事件对象
public class RegisterSuccessEvent extends ApplicationEvent {//Object source :待会要传递的参数public RegisterSuccessEvent(Object source) {super(source);}
}

第二步,创建事件监听器,监听RegisterSuccessEvent事件

//注册成功的监听器,监听 RegisterSuccessEvent事件
@Slf4j
@Service
public class RegisterSuccessListenner implements ApplicationListener<RegisterSuccessEvent> {@Overridepublic void onApplicationEvent(RegisterSuccessEvent event) {Map<String,String> source = (Map<String, String>) 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<String,String> 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<ContextRefreshedEvent> ,监听事件是Spring容器刷新完成,然后复写onApplicationEvent方法,在该方法中往数据库保存管理员账号即可。你可以自己试着写一下。

5.3.web三大监听器

这里给大家介绍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<RequestAttributes> requestAttributesHolder 中存储起来,所以我们在业务代码中可以通过RequestContextHolder直接获取Request对象。

6.SpringBoot的事件

讲完Spirng事件我们来讲SpringBoot中的事件,在SpringBoot启动流程中出现 SpringApplicationRunListeners#starting 等代码,其实就是在发布SpringBoot的事件。我们来看

4.1.SpringApplicationRunListeners

SpringApplicationRunListeners : 下面是 SpringApplicationRunListeners的代码结构

class SpringApplicationRunListeners {private final Log log;//保存了很多的SpringApplicationRunListenerprivate final List<SpringApplicationRunListener> listeners;SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {this.log = log;this.listeners = new ArrayList<>(listeners);}//调用starting,本质是调用SpringApplicationRunListener的startingvoid starting() {for (SpringApplicationRunListener listener : this.listeners) {listener.starting();}}//调用environmentPrepared环境准备完成,本质是调用SpringApplicationRunListener的environmentPreparedvoid environmentPrepared(ConfigurableEnvironment environment) {for (SpringApplicationRunListener listener : this.listeners) {listener.environmentPrepared(environment);}}//调用contextPrepared 容器刷新完成,本质是调用SpringApplicationRunListener的contextPreparedvoid 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<SpringApplicationRunListener> listeners;集合,SpringApplicationRunListeners#starting方法本质是调用的SpringApplicationRunListener#starting方法

4.2.SpringApplicationRunListener

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;@Overridepublic void starting() {//通过 SimpleApplicationEventMulticaster 发布ApplicationStartingEvent 应用开始启动事件this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));}@Overridepublic void environmentPrepared(ConfigurableEnvironment environment) {//通过 SimpleApplicationEventMulticaster 发布ApplicationEnvironmentPreparedEvent 应用环境准备完成事件this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));}@Overridepublic void contextPrepared(ConfigurableApplicationContext context) {//通过 SimpleApplicationEventMulticaster 发布ApplicationContextInitializedEvent 应用初始化完成事件this.initialMulticaster.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));}@Overridepublic 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));}@Overridepublic void started(ConfigurableApplicationContext context) {//通过 ConfigurableApplicationContext 发布ApplicationStartedEvent 应用启动成功事件context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));}@Overridepublic void running(ConfigurableApplicationContext context) {//通过 ConfigurableApplicationContext 发布ApplicationReadyEvent 应用启动完毕,可以接受请求了context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));}@Overridepublic 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 cancontext.publishEvent(event);}else {// An inactive context may not have a multicaster so we use our multicaster to// call all of the context's listeners insteadif (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);//打印错误@Overridepublic 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 {...省略...@Overridepublic void multicastEvent(ApplicationEvent event) {multicastEvent(event, resolveDefaultEventType(event));}@Overridepublic 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#onApplicationEventlistener.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 necessaryApplicationEvent 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 initializedif (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;@Overridepublic 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() < STRAP_LINE_SIZE - (version.length() + SPRING_BOOT.length())) {padding.append(" ");}//打印版本号printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT, AnsiColor.DEFAULT, padding.toString(),AnsiStyle.FAINT, version));printStream.println();}}

ImageBanner : 用来打印图片横幅,我们只需要在resources下创建一个 banner.gif ,就可以被打印,也可以使用spring.banner.image.location 来指定横幅的位置。

spring:main:banner-mode: console #横幅模式,在控制台打印 off是关闭banner:image:location: classpath:banner.gif #图片横幅,默认也是banner.gif#height: #width:

ImageBanner结构如下

public class ImageBanner implements Banner {private static final String PROPERTY_PREFIX = "spring.banner.image.";private static final Log logger = LogFactory.getLog(ImageBanner.class);private static final double[] RGB_WEIGHT = { 0.2126d, 0.7152d, 0.0722d };private final Resource image;//打印横幅private void printBanner(Environment environment, PrintStream out) throws IOException {//处理图片属性int width = getProperty(environment, "width", Integer.class, 76);int height = getProperty(environment, "height", Integer.class, 0);int margin = getProperty(environment, "margin", Integer.class, 2);boolean invert = getProperty(environment, "invert", Boolean.class, false);BitDepth bitDepth = getBitDepthProperty(environment);PixelMode pixelMode = getPixelModeProperty(environment);Frame[] frames = readFrames(width, height);for (int i = 0; i < frames.length; i++) {if (i > 0) {resetCursor(frames[i - 1].getImage(), out);}//打印printBanner(frames[i].getImage(), margin, invert, bitDepth, pixelMode, out);sleep(frames[i].getDelayTime());}}
}

7.2.横幅打印流程

横幅的打印是在SpringBoot启动流程中,调用 org.springframework.boot.SpringApplication#printBanner方法实现


private Banner printBanner(ConfigurableEnvironment environment) {//判断横幅功能是否被关闭,对应yaml中的 spring.main.banner-mode=offif (this.bannerMode == Banner.Mode.OFF) {return null;}//资源加载器,比如:用来加载banner.txtResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader: new DefaultResourceLoader(getClassLoader());//创建 Banner打印器SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);if (this.bannerMode == Mode.LOG) {//如果是log模式走这,对应:spring.main.banner-mode=logreturn bannerPrinter.print(environment, this.mainApplicationClass, logger);}//默认是console模式,走这return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}

代码中会判断 this.bannerMode == Banner.Mode.OFF 时候开启日志打印(对应yaml中的配置),默认是Console,然后会创建一个ResourceLoader,主要是用来加载banner.txt或banner.gif 资源文件的。然后会判断如果this.bannerMode == Mode.LOG就使用logger打印,否则使用System.out打印,默认是System.out。打印横幅调用的是SpringApplicationBannerPrinter#print方法完成的。代码来到org.springframework.boot.SpringApplicationBannerPrinter#print

class SpringApplicationBannerPrinter {//banner.txt的地址static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";//banner.gif的地址static final String BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location";//banner文件的名字static final String DEFAULT_BANNER_LOCATION = "banner.txt";//图片bannre的支持的后缀static final String[] IMAGE_EXTENSION = { "gif", "jpg", "png" };//默认的banner,使用SpringBootBannerprivate static final Banner DEFAULT_BANNER = new SpringBootBanner();private final ResourceLoader resourceLoader;Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {//获取BannerBanner banner = getBanner(environment);//打印Bannerbanner.printBanner(environment, sourceClass, out);return new PrintedBanner(banner, sourceClass);}private Banner getBanner(Environment environment) {Banners banners = new Banners();//尝试获取image Banner,默认banner.gif,加入Bannersbanners.addIfNotNull(getImageBanner(environment));//尝试获取banner.txt ,加入Bannersbanners.addIfNotNull(getTextBanner(environment));if (banners.hasAtLeastOneBanner()) {//返回多个bannersreturn banners;}if (this.fallbackBanner != null) {return this.fallbackBanner;}//如果没有指定banner.txt或者banner.gif,就会使用默认的SpringBootBannerreturn DEFAULT_BANNER;}private Banner getTextBanner(Environment environment) {//以:获取banner的location:spring.banner.location String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);//加载 banner.txtResource resource = this.resourceLoader.getResource(location);if (resource.exists()) {//返回bannerreturn new ResourceBanner(resource);}//如果不存在banner.txt,那么就会使用默认的SpringBootBanner去打印return null;}private Banner getImageBanner(Environment environment) {//获取图片banner的地址:spring.banner.image.locationString location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY);if (StringUtils.hasLength(location)) {//加载图片,banner.gifResource resource = this.resourceLoader.getResource(location);return resource.exists() ? new ImageBanner(resource) : null;}for (String ext : IMAGE_EXTENSION) {Resource resource = this.resourceLoader.getResource("banner." + ext);if (resource.exists()) {return new ImageBanner(resource);}}return null;}

SpringApplicationBannerPrinter会尝试获取spring.banner.image.location , 默认指向的resources/banner.gif 以及 spring.banner.location 指向的resources/banner.txt ,然后送resourceLoader加载资源文件,封装成 ImageBanner 或者 ResourceBanner。并交给Banners,Banners维护了Banner的列表。也就是说可以同时打印多个横幅,我们可以通过修改这2个路径来指定banner资源的位置。

如果没有banner.txt 和 banner.gif ,就会使用SpringBootBanner打印默认的banner.

紧接着就会调用Banners#printBanner方法循环执行每个Banner#printBanner方法。具体的Banner是如何打印出来的上面ImageBanner和SpringBootBanner已有介绍。

8.容器创建

8.1.容器介绍

SpringBoot底层是Spring,启动的过程中肯定要创建Spring容器,下面简单介绍一下IOC的容器工厂类

  • BeanFactory: Spring的容器顶层工厂,提供了获取Bean的基础方法,所有的IOC容器都是其子类
  • XmlWebApplicationContext : 整合web的容器对象,默认从’/WEB-INF/applicationContext.xml’加载配置
  • ClassPathXmlApplicationContext : 使用ResourcePatternResolver,默认从classpath加载xml配置的容器对象
  • AnnotationConfigApplicationContext :针对于注解的容器对象,SpringBoot使用它

8.2.容器的创建

创建容器是在SpringBoot启动流程中的 org.springframework.boot.SpringApplication#createApplicationContext方法

protected ConfigurableApplicationContext createApplicationContext() {Class<?> contextClass = this.applicationContextClass;if (contextClass == null) {try {switch (this.webApplicationType) {case SERVLET:contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);break;case REACTIVE:contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);break;default://默认走这,使用的是AnnotationConfigApplicationContextcontextClass = Class.forName(DEFAULT_CONTEXT_CLASS);}}catch (ClassNotFoundException ex) {throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);}}//反射创建AnnotationConfigApplicationContext的实例return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

9.容器刷新

9.1.容器刷新

在SpringBoot启动流程中 有一句代码 SpringApplication#prepareContext, 这句代码在准备Spring容器,代码如下org.springframework.boot.SpringApplication#prepareContext

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {//把environment绑定到容器context.setEnvironment(environment);//容器的后置处理,里面也没干啥postProcessApplicationContext(context);//触发ApplicationContextInitializer#initialize的调用applyInitializers(context);//触发监听器,发布ApplicationContextInitializedEvent事件listeners.contextPrepared(context);if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);logStartupProfileInfo(context);}// Add boot specific singleton beansConfigurableListableBeanFactory beanFactory = context.getBeanFactory();//把applicationArguments注册Bean我们可以通过注入 ApplicationArguments 来获取程序启动参数beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {//把Bnaner注册为BeanbeanFactory.registerSingleton("springBootBanner", printedBanner);}if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory) beanFactory)//如果出现相同的Bean,是否允许覆盖,默认为false,//可用在yaml通过spring.main.allow-bean-definition-overriding: true来指定.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}if (this.lazyInitialization) {//延迟初始化context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}// Load the sources//配置类资源Set<Object> sources = getAllSources();Assert.notEmpty(sources, "Sources must not be empty");//load启动类,把启动类注册为Beanload(context, sources.toArray(new Object[0]));//抛出ApplicationPreparedEvent事件listeners.contextLoaded(context);
}

9.2.容器刷新

容器刷新在 org.springframework.boot.SpringApplication#refreshContext 方法中

private void refreshContext(ConfigurableApplicationContext context) {//刷新容器refresh(context);if (this.registerShutdownHook) {try {//注册一个钩子,当容器被关闭,context.registerShutdownHook();}catch (AccessControlException ex) {// Not allowed in some environments.}}
}

上面代码做了两个事情,一个是调用refresh方法刷新容器,二个是向 JVM 的Runtime 注册一个名为SpringContextShutdownHook的关闭钩子,在 JVM 关闭时关闭此上下文,委托容器的 doClose()执行实际的关闭过程。下面是context.registerShutdownHook();的内部代码,见:org.springframework.context.support.AbstractApplicationContext#registerShutdownHook

 @Overridepublic void registerShutdownHook() {if (this.shutdownHook == null) {// No shutdown hook registered yet.this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {@Overridepublic void run() {synchronized (startupShutdownMonitor) {doClose();}}};//Runtime: 每个 Java 应用程序都有一个Runtime类的实例,它允许应用程序与运行应用程序的环境进行交互 //给Runtime 注册SpringContextShutdownHook 钩子Runtime.getRuntime().addShutdownHook(this.shutdownHook);}}protected void doClose() {// Check whether an actual close attempt is necessary...if (this.active.get() && this.closed.compareAndSet(false, true)) {if (logger.isDebugEnabled()) {logger.debug("Closing " + this);}LiveBeansView.unregisterApplicationContext(this);try {//发布 ContextClosedEvent 容器关闭事件// Publish shutdown event.publishEvent(new ContextClosedEvent(this));}catch (Throwable ex) {logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);}// Stop all Lifecycle beans, to avoid delays during individual destruction.if (this.lifecycleProcessor != null) {try {//生命周期处理器,close触发this.lifecycleProcessor.onClose();}catch (Throwable ex) {logger.warn("Exception thrown from LifecycleProcessor on context close", ex);}}// Destroy all cached singletons in the context's BeanFactory.//销毁所有的单利BeandestroyBeans();//close工厂// Close the state of this context itself.closeBeanFactory();//空实现,让子类做一些最后的清理...// Let subclasses do some final clean-up if they wish...onClose();// Reset local application listeners to pre-refresh state.if (this.earlyApplicationListeners != null) {//监听器清理this.applicationListeners.clear();this.applicationListeners.addAll(this.earlyApplicationListeners);}// Switch to inactive.//下文当前是否处于活动状态的标志修改为falsethis.active.set(false);}}

总结一下上面代码:就是给JVM的Runtime注册一个SpringContextShutdownHook钩子,当程序关闭,会触发AbstractApplicationContext#doClose方法,该方法中最了一些销毁和扫尾工作。

接下来就是刷新容器方法 refresh 分析了,代码最终来到 AbstractApplicationContext#refresh 方法中。

@Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing.//1.准备刷新工作 ,记录开始时间,初始化属性,校验配置文件,准备事件的存储SetprepareRefresh();// Tell the subclass to refresh the internal bean factory.//告诉子类,刷新Bean工厂,销毁旧beanFactory,创建新beanFactory,默认DefaultListableBeanFactory//从子容器的refreshBeanFactory方法中载入Bean的资源文件//2.加载配置,加载Bean,注册Bean就在这儿ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// Prepare the bean factory for use in this context.//3.准备工厂,配置工厂的下文特性, 例如上下文的 ClassLoader 和后处理器。Bean表达式解析器,//BeanPostProcessor和 Aware类的自动装配等prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.//4.BeanFactory初始化完成的后置工作,//这是一个空方法,留给三方框架或者自己配置,作用是允许对beanFoctory进行扩展处理postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.//5.调用BeanFactory的后置处理器BeanFactoryPostProcessor,在 bean定义注册之后bean实例化之前调用invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.//6.注册Bean的后置处理器BeanPostProcessor,在Bean初始化前,后执行registerBeanPostProcessors(beanFactory);// Initialize message source for this context.//7.初始化信息源,国际化相关initMessageSource();// Initialize event multicaster for this context.//8.初始化容器事件传播器,比如:SimpleApplicationEventMulticasterinitApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.//9.空方法,该方法子类实现,在容器刷新的时候可以自定义逻辑;如创建Tomcat,Jetty等WEB服务器onRefresh();// Check for listener beans and register them.//10.注册事件监听器,注册实现了ApplicationListener接口的监听器bean,//这些监听器是注册到ApplicationEventMulticaster中的registerListeners();// Instantiate all remaining (non-lazy-init) singletons.//11.实例化所有剩余的(非延迟初始化)单例的Bean//也就是创建Bean的真正实例就在该方法中finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.//12.完成context的刷新。主要是调用LifecycleProcessor的onRefresh()方法,并且发布事件(ContextRefreshedEvent)finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.//销毁已经创建的单例Bean。destroyBeans();// Reset 'active' flag.//取消容器刷新cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...//重置缓存resetCommonCaches();}}}

这里其实已经在调用Spirng的容器刷新流程了,这里大概说明一下,具体的详细流程见

  1. 在obtainFreshBeanFactory方法中回去解析配置文件,然后把Bean封装成BeanDefinition,注册到IOC容器中(一个CurrentHashMap)
  2. 在invokeBeanFactoryPostProcessors方法中会触发BeanFactory的后置处理器的调用,这个很有用,可以在BeanFactory初始化过程中做扩展
  3. registerBeanPostProcessors 方法中注册了BeanPostProcessors后置处理器,这个很有用,可以在Bean实例化前后,做一些扩展业务,比如:@Transactionl ,@Autowired的实现原理就是基于Bean的后置处理器
  4. finishBeanFactoryInitialization :这个方法中会对单利的Bean做正在的实例化,然后缓存到Spring的IOC容器中,以后获取Bean的时候会从缓存中取。这保证了Bean的单利。

10.callRunners

10.1.认识ApplicationRunner 和 CommandLineRunner

SpringBoot启动完成之后的最后一步就是org.springframework.boot.SpringApplication#callRunners,该方法中回去执行所有的ApplicationRunner , 和 CommandLineRunner 。

private void callRunners(ApplicationContext context, ApplicationArguments args) {List<Object> runners = new ArrayList<>();runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());AnnotationAwareOrderComparator.sort(runners);for (Object runner : new LinkedHashSet<>(runners)) {if (runner instanceof ApplicationRunner) {callRunner((ApplicationRunner) runner, args);}if (runner instanceof CommandLineRunner) {callRunner((CommandLineRunner) runner, args);}}}

ApplicationRunner 和 CommandLineRunner 可以看做是SpringBoot的扩展,两个接口的功能是一样的,通过实现该接口可以在SpringBoot启动成功之后做一些初始化工作。

这两个Runner的不同之处在于CommandLineRunner#run方法接收的是main方法传递过来的直接参数args,而ApplicationRunner#run方法接收的是封装了args的ApplicationArguments对象。我们来演示其中一个

10.2.使用ApplicationRunner

第一步:定义一个Runner类

@Component
@Slf4j
public class ApplicationRunnerBean implements ApplicationRunner {@Overridepublic void run(ApplicationArguments args) throws Exception {log.info("ApplicationRunnerBean.run SpringBoot启动完成,做一些初始化...");}
}

第二步:启动测试

文章结束啦,如果文章对你有所帮助,请一定给个好评哦,请一定给个好评哦,请一定给个好评哦

SpringBoot入门到精通-SpringBoot启动流程(七)相关推荐

  1. SpringBoot入门到精通_第6篇 _必知必会

    接上一篇:SpringBoot入门到精通_第5篇 _SpringBoot Actuator监控 https://blog.csdn.net/weixin_40816738/article/detail ...

  2. SpringBoot入门到精通_第5篇 _SpringBoot Actuator监控

    接上一篇:SpringBoot入门到精通_第4篇 _开发三板斧 https://blog.csdn.net/weixin_40816738/article/details/101097161 文章目录 ...

  3. SpringBoot入门到精通_第4篇 _开发三板斧

    接上一篇:SpringBoot入门到精通_第3篇 _应用组件分析 https://blog.csdn.net/weixin_40816738/article/details/101096218 文章目 ...

  4. SpringBoot入门到精通_第3篇 _应用组件分析

    接上一篇:SpringBoot入门到精通_第2篇 _1分钟实战需求项目 https://blog.csdn.net/weixin_40816738/article/details/101095964 ...

  5. SpringBoot入门到精通_第2篇 _1分钟实战需求项目

    接上一篇:SpringBoot入门到精通_第1篇 _核心概念 https://blog.csdn.net/weixin_40816738/article/details/94916051 文章目录 一 ...

  6. SpringBoot入门到精通 idea教学 (余胜军通俗易懂版本)

    Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置.通过 ...

  7. SpringBoot入门到精通_第7篇 _必知必会总结

    接上一篇:SpringBoot入门到精通_第6篇 _必知必会

  8. SpringBoot入门到精通_第1篇 _核心概念

    SpringBoot 必知必会 核心精粹 文章目录 一.SpringBoot必知必会 1. 是什么?能做什么? 2. 有哪些特性? 一.SpringBoot必知必会 1. 是什么?能做什么? 是什么? ...

  9. SpringBoot(十二)启动流程分析之创建应用上下文AnnotationConfigServletWebServerApplicationContext

    SpringBoot版本:2.1.1      ==>启动流程分析汇总 接上篇博客Spring Boot 2.1.1(十一)启动流程分析之设置系统属性spring.beaninfo.ignore ...

最新文章

  1. 【EMC】EMC屏蔽设计
  2. 算法基础知识科普:8大搜索算法之二叉搜索树(上)
  3. 词移距离 Word Mover‘s Distance
  4. 文本聊天室(TCP-中)
  5. HFileOutputFormat与TotalOrderPartitioner
  6. Mysql 休眠连接过多,有可能导致“Too many connections”的错误
  7. creator 静态属性_CocosCreator cc.class声明类
  8. 什么镜头最适合拍风景_哪种镜头最适合你的街头摄影?
  9. qt执行命令行失败_QT中QProcess调用命令行的痛苦经历
  10. Google 杀死了 160 个产品!
  11. js中this的问题
  12. 树莓派(Linux)与镜像源
  13. 在多媒体计算机系统中图像的颜色是,图像量化位数越大,记录图像中每个像素点的颜色种类就越多。() - 试题答案网问答...
  14. EMNLP-21-Exploring Task Difficulty for Few-Shot Relation Extraction
  15. Java项目:ssm学生学籍管理系统
  16. 6.22 android计算字符高度宽度,红蓝3D图片的制作原理及NDK生成实现
  17. Java 关于java.util.LinkedHashMap cannot be cast to 实体类问题答案
  18. 《算法笔记》胡凡——4.2散列
  19. 如何定位web前后台的BUG
  20. win10在几个窗口间切换的快捷键

热门文章

  1. 创意发明:单片机做的半导体智能制冷小冰箱 原理图和程序源代码等技术文件
  2. 基于c语言实现的个人理财系统,基于Android的个人理财系统—设计和实现-论文最终版.doc...
  3. 开车,开车,打疫苗以为能收获爱情,结果收到了警方提示,我哭了
  4. 教师资格证是计算机考试地点,教师资格证考试必须有计算机模块证吗?
  5. 首席财务官如何看待云计算和人工智能的财务报告
  6. java_网络编程学习笔记(一)
  7. iOS:crash崩溃日志分析
  8. 我所知道的富士康之二:出门
  9. Python 3 字符串 maketrans( ) 方法
  10. ASA与FTD的基本配置