SpringBoot入门到精通-SpringBoot启动流程

Posted 墨家巨子@俏如来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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