SpringBoot入门到精通-SpringBoot启动流程(七)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 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 异常报告器的list
        Collection<SpringBootExceptionReporter> 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;
    }

画一个流程图总结一下

在这里插入图片描述

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.
     */
    //任务的ID
    private 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. */
    //当前任务名
    @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.
     * <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;

    //报告指定的异常
    @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<FailureAnalyzer> 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<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);

@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<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>...省略{
   
   
    @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 ,拥有了事件发布的能力。

他们的继承体系如下

在这里插入图片描述

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> {
   
   

    @Override
    public 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;
    //保存了很多的SpringApplicationRunListener
    private final List<SpringApplicationRunListener> listeners;

    SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> 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<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;

    @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() < 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=off
    if (this.bannerMode == Banner.Mode.OFF) {
   
   
        return null;
    }
    //资源加载器,比如:用来加载banner.txt
    ResourceLoader 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=log
        return 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,使用SpringBootBanner
    private static final Banner DEFAULT_BANNER = new SpringBootBanner();

    private final ResourceLoader resourceLoader;


    Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
   
   
        //获取Banner
        Banner banner = getBanner(environment);
        //打印Banner
        banner.printBanner(environment, sourceClass, out);
        return new PrintedBanner(banner, sourceClass);
    }

    private Banner getBanner(Environment environment) {
   
   
        Banners banners = new Banners();
        //尝试获取image Banner,默认banner.gif,加入Banners
        banners.addIfNotNull(getImageBanner(environment));
        //尝试获取banner.txt ,加入Banners
        banners.addIfNotNull(getTextBanner(environment));
        if (banners.hasAtLeastOneBanner()) {
   
   
            //返回多个banners
            return banners;
        }
        if (this.fallbackBanner != null) {
   
   
            return this.fallbackBanner;
        }
        //如果没有指定banner.txt或者banner.gif,就会使用默认的SpringBootBanner
        return DEFAULT_BANNER;
    }

    private Banner getTextBanner(Environment environment) {
   
   
        //以:获取banner的location:spring.banner.location 
        String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
        //加载 banner.txt
        Resource resource = this.resourceLoader.getResource(location);
        if (resource.exists()) {
   
   
            //返回banner
            return new ResourceBanner(resource);
        }
        //如果不存在banner.txt,那么就会使用默认的SpringBootBanner去打印
        return null;
    }

    private Banner getImageBanner(Environment environment) {
   
   
        //获取图片banner的地址:spring.banner.image.location
        String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY);
        if (StringUtils.hasLength(location)) {
   
   
            //加载图片,banner.gif
            Resource 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:
                    //默认走这,使用的是AnnotationConfigApplicationContext
                    contextClass = 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 beans
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    //把applicationArguments注册Bean我们可以通过注入 ApplicationArguments 来获取程序启动参数
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
   
   
        //把Bnaner注册为Bean
        beanFactory.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启动类,把启动类注册为Bean
    load(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

    @Override
    public void registerShutdownHook() {
   
   
        if (this.shutdownHook == null) {
   
   
            // No shutdown hook registered yet.
            this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
   
   
                @Override
                public 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.
            //销毁所有的单利Bean
            destroyBeans();
            //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.
             //下文当前是否处于活动状态的标志修改为false
            this.active.set(false);
        }
    }

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

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

@Override
   public void refresh() throws BeansException, IllegalStateException {
   
   
       synchronized (this.startupShutdownMonitor) {
   
   
           // Prepare this context for refreshing.
           //1.准备刷新工作 ,记录开始时间,初始化属性,校验配置文件,准备事件的存储Set
           prepareRefresh();

           // 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.初始化容器事件传播器,比如:SimpleApplicationEventMulticaster
               initApplicationEventMulticaster();

               // 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 {
   
   

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

第二步:启动测试

在这里插入图片描述

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

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
7月前
|
前端开发 Java 应用服务中间件
SpringBoot-Run启动流程
探索Spring Boot启动流程,从加载配置、创建应用上下文、自动配置到启动内嵌服务器。启动类是入口点,`@SpringBootApplication`标记启动,`SpringApplication.run`启动应用。自动配置基于条件注解配置Bean,应用上下文由`SpringApplication`创建并刷新。内嵌服务器如Tomcat随应用启动,简化部署。理解此流程有助于深入掌握Spring Boot。
223 2
|
7月前
|
缓存 Java 程序员
springboot的启动流程总结
springboot的启动流程总结
|
7月前
|
设计模式 Java 容器
SpringBoot2 | SpringBoot启动流程源码分析(二)
SpringBoot2 | SpringBoot启动流程源码分析(二)
85 0
|
7月前
|
设计模式 Java 机器人
SpringBoot3自动配置流程 SPI机制 核心注解 自定义starter
SpringBoot3自动配置流程 SPI机制 核心注解 自定义starter
|
2月前
|
前端开发 Java 数据安全/隐私保护
用户登录前后端开发(一个简单完整的小项目)——SpringBoot与session验证(带前后端源码)全方位全流程超详细教程
文章通过一个简单的SpringBoot项目,详细介绍了前后端如何实现用户登录功能,包括前端登录页面的创建、后端登录逻辑的处理、使用session验证用户身份以及获取已登录用户信息的方法。
410 2
用户登录前后端开发(一个简单完整的小项目)——SpringBoot与session验证(带前后端源码)全方位全流程超详细教程
|
6月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的销售项目流程化管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的销售项目流程化管理系统附带文章源码部署视频讲解等
71 3
|
2月前
|
NoSQL Java Redis
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
这篇文章介绍了如何使用Spring Boot整合Apache Shiro框架进行后端开发,包括认证和授权流程,并使用Redis存储Token以及MD5加密用户密码。
42 0
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
|
2月前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
232 2
|
2月前
|
JSON 前端开发 JavaScript
优雅!Spring Boot 3.3 实现职责链模式,轻松应对电商订单流程
本文介绍如何使用 Spring Boot 3.3 实现职责链模式,优化电商订单处理流程。通过将订单处理的各个环节(如库存校验、优惠券核验、支付处理等)封装为独立的处理器,并通过职责链将这些处理器串联起来,实现了代码的解耦和灵活扩展。具体实现包括订单请求类 `OrderRequest`、抽象处理器类 `OrderHandler`、具体处理器实现(如 `OrderValidationHandler`、`VerifyCouponHandler` 等)、以及初始化职责链的配置类 `OrderChainConfig`。
|
4月前
|
XML Java 应用服务中间件
SpringBoot启动流程解析
SpringBoot启动流程解析
64 0