ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

深入学习Spring Boot中的SpringApplication

深入学习Spring Boot中的SpringApplication 一、自定义SpringApplication默认的我们都是直接通过SpringApplication的run方法来直接启动Spring Boot其实我们可以通过一些API来调整某些行为。1.1 通过SpringApplication API调整我们新建一个SpringBoot项目Spring Boot版本为2.1.0.RELEASEartifactId为SpringApplication并引入spring-boot-starter-web依赖。项目结构如下所示:Let’s modify the code in the entry class as follows:SpringApplicationapplicationnewSpringApplication(DemoApplication.class);application.setBannerMode(Banner.Mode.OFF);application.setWebApplicationType(WebApplicationType.SERVLET);application.setAdditionalProfiles(dev);application.run(args);通过调用SpringApplication的方法我们关闭了Banner的打印设置应用环境为非WEB应用profiles指定为dev。除此之外SpringApplication还包含了许多别的方法具体可以查看源码或者官方文档1.2 通过SpringApplicationBuilder API调整SpringApplicationBuilder提供了Fluent API可以实现链式调用下面的代码和上面的效果一致但在编写上较为方便newSpringApplicationBuilder(Application.class).bannerMode(Banner.Mode.OFF).web(WebApplicationType.SERVLET).profiles(dev).addCommandLineProperties(false).run(args);二、SpringApplication准备阶段SpringApplication的生命周期阶段大致可以分为准备阶段和运行阶段。我们通过源码来查看SpringApplication的有参构造器通过有参构造器里的代码我们可以将SpringApplication的准备阶段分为以下几个步骤2.1 配置源构造器中this.primarySources new LinkedHashSet(Arrays.asList(primarySources));这行代码用于加载我们配置的Spring Boot Bean源。通常我们使用SpringApplication或者SpringApplicationBuilder的构造器来直接指定源。所谓的Spring Boot Bean源指的是某个被SpringBootApplication注解标注的类比如入口类SpringBootApplicationpublicclassApplication{publicstaticvoidmain(String[]args){SpringApplicationappnewSpringApplication(Application.class);app.run(args);}}我们也可以将上面的代码改为下面这种方式publicclassApplication{publicstaticvoidmain(String[]args){SpringApplicationappnewSpringApplication(ApplicationResource.class);app.run(args);}SpringBootApplicationpublicstaticclassApplicationResource{}}这样也是可行的。查看SpringApplication的单个参数构造器说明我们除了配置单个源外还可以配置多个源。2.2 推断应用类型构造器中这行this.webApplicationType WebApplicationType.deduceFromClasspath();代码用于推断当前Spring Boot应用类型。Spring Boot 2.0后应用可以分为下面三种类型WebApplicationType.NONE非WEB类型WebApplicationType.REACTIVEWeb Reactive类型WebApplicationType.SERVLETWeb Servlet类型。WebApplicationType.deduceFromClasspath()方法根据当前应用ClassPath中是否存在相关的实现类来判断应用类型到底是哪个deduceFromClasspath方法的源码如下所示:我们也可以直接通过SpringApplication的setWebApplicationType方法或者SpringApplicationBuilder的web方法来指定当前应用的类型。2.3 加载应用上下文初始器接着下一行代码setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));用于加载应用上下文初始器ApplicationContextInitializer。getSpringFactoriesInstances方法的源码如下所示上面代码利用Spring工厂加载机制实例化ApplicationContextInitializer实现类并进行排序。所以我们可以通过实现ApplicationContextInitializer接口用于在Spring Boot应用初始化之前执行一些自定义操作。举个例子在com.example.demo下新建initializer包然后创建一个HelloApplicationContextInitializer类实现ApplicationContextInitializer接口Order(Ordered.HIGHEST_PRECEDENCE)publicclassHelloApplicationContextInitializerimplementsApplicationContextInitializer{Overridepublicvoidinitialize(ConfigurableApplicationContextapplicationContext){System.out.println(ConfigurableApplicationContext.id - applicationContext.getId());}}上面代码中实现了initialize方法并且使用Order注解指定优先级。其中Ordered.HIGHEST_PRECEDENCE等于Integer.MIN_VALUEOrdered.LOWEST_PRECEDENCE等于Integer.MAX_VALUE。所以数值越小优先级越高。除了使用Order注解来指定优先级外我们也可以通过实现org.springframework.core.Ordered接口的getOrder方法来指定优先级。接着我们来创建一个优先级比HelloApplicationContextInitializer低的Initializer —— AfterHelloApplicationContextInitializerpublicclassAfterHelloApplicationContextInitializerimplementsApplicationContextInitializer,Ordered{Overridepublicvoidinitialize(ConfigurableApplicationContextapplicationContext){System.out.println(AfterHelloApplicationContextInitializer: applicationContext.getId());}OverridepublicintgetOrder(){returnOrdered.LOWEST_PRECEDENCE;}}上面通过getOrder方法来指定了优先级为最低优先级。创建好后我们还需在工厂配置文件里配置这两个实现类。在resources目录下新建META-INF目录并创建spring.factories文件# Initializers org.springframework.context.ApplicationContextInitializer\ com.wno704.boot.HelloApplicationContextInitializer,\ com.wno704.boot.AfterHelloApplicationContextInitializer这时候启动Spring Boot项目会发现控制台在打印Banner后就执行了这两个初始化器并且HelloApplicationContextInitializer的initialize方法执行时机先于AfterHelloApplicationContextInitializer的initialize方法2.4 加载应用事件监听器在加载完应用上下文初始器后下一行的setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));代码加载了应用事件监听器。与加载事件上下文初始器类似Spring Boot也是通过Spring的工厂方法来实例化ApplicationListener的实现类并进行排序。既然是事件监听那么其可以监听什么事件呢其监听的是ApplicationEvent接口的实现类我们查看一下都有哪些事件实现了这个接口这里我们以ContextClosedEvent为例子来编写自定义的应用事件监听器监听Spring上下文关闭事件。在com.wno704.boot下新建listener包然后创建一个ContextClosedEventListener类实现ApplicationListener接口Order(Ordered.HIGHEST_PRECEDENCE)publicclassContextClosedEventListenerimplementsApplicationListenerContextClosedEvent{OverridepublicvoidonApplicationEvent(ContextClosedEventevent){System.out.println(ContextClosedEvent: event.getApplicationContext().getId());}}上面代码实现了对ContextClosedEvent事件的监听并且分配了最高优先级。接着创建一个优先级比ContextClosedEventListener低的监听器AfterContextClosedEventListenerpublicclassAfterContextClosedEventListenerimplementsApplicationListenerContextClosedEvent,Ordered{OverridepublicintgetOrder(){returnOrdered.HIGHEST_PRECEDENCE1;}OverridepublicvoidonApplicationEvent(ContextClosedEventevent){System.out.println(AfterContextClosedEventr: event.getApplicationContext().getId());}}最后别忘了在Spring工厂配置文件里进行配置# Application Listeners org.springframework.context.ApplicationListener\ com.wno704.boot.listener.ContextClosedEventListener,\ com.wno704.boot.listener.AfterContextClosedEventListener运行Spring Boot入口类控制台输出如下2.5 推断入口类接着构造器里的代码下一行this.mainApplicationClass deduceMainApplicationClass();用于推断运行Spring Boot应用的入口类。查看deduceMainApplicationClass方法源码代码主要逻辑是根据Main线程执行堆栈判断实际的入口类。准备阶段介绍完毕后接下来开始介绍运行阶段。三、SpringApplication运行阶段SpringApplication的运行阶段对应SpringApplication的run方法我们查看其源码运行阶段大致可以分为下面这几个过程3.1 开启时间监听run方法开头的这两行代码用于开启时间监听StopWatchstopWatchnewStopWatch();stopWatch.start();上面代码用于开启Spring Boot应用启动时间监听配合下面的stopWatch.stop();便可以计算出完整的启动时间。run方法的这几行代码用于加载Spring应用运行监听器SpringApplicationRunListenerSpringApplicationRunListenerslistenersgetRunListeners(args);listeners.started();getRunListeners方法源码上面代码通过SpringFactoriesLoader检索META-INF/spring.factories找到声明的所有SpringApplicationRunListener的实现类并将其实例化然后装配到List运行监听器集合中。listeners.started();用于遍历运行监听器集合中的所有SpringApplicationRunListener的实现类并逐一调用它们的starting方法广播Spring Boot应用要开始启动了。在Spring Boot中SpringApplicationRunListener接口用于监听整个Spring Boot应用生命周期其代码如下所示/** * A collection of {link SpringApplicationRunListener}. * * author Phillip Webb */classSpringApplicationRunListeners{privatefinalLoglog;privatefinalListSpringApplicationRunListenerlisteners;SpringApplicationRunListeners(Loglog,Collection?extendsSpringApplicationRunListenerlisteners){this.loglog;this.listenersnewArrayList(listeners);}voidstarting(){for(SpringApplicationRunListenerlistener:this.listeners){listener.starting();}}voidenvironmentPrepared(ConfigurableEnvironmentenvironment){for(SpringApplicationRunListenerlistener:this.listeners){listener.environmentPrepared(environment);}}voidcontextPrepared(ConfigurableApplicationContextcontext){for(SpringApplicationRunListenerlistener:this.listeners){listener.contextPrepared(context);}}voidcontextLoaded(ConfigurableApplicationContextcontext){for(SpringApplicationRunListenerlistener:this.listeners){listener.contextLoaded(context);}}voidstarted(ConfigurableApplicationContextcontext){for(SpringApplicationRunListenerlistener:this.listeners){listener.started(context);}}voidrunning(ConfigurableApplicationContextcontext){for(SpringApplicationRunListenerlistener:this.listeners){listener.running(context);}}voidfailed(ConfigurableApplicationContextcontext,Throwableexception){for(SpringApplicationRunListenerlistener:this.listeners){callFailedListener(listener,context,exception);}}privatevoidcallFailedListener(SpringApplicationRunListenerlistener,ConfigurableApplicationContextcontext,Throwableexception){try{listener.failed(context,exception);}catch(Throwableex){if(exceptionnull){ReflectionUtils.rethrowRuntimeException(ex);}if(this.log.isDebugEnabled()){this.log.error(Error handling failed,ex);}else{Stringmessageex.getMessage();message(message!null)?message:no error message;this.log.warn(Error handling failed (message));}}}}这些方法对应着Spring Boot应用生命周期的各个阶段方法名称对应生命周期Spring Boot起始版本starting()Spring应用刚启动1.0environmentPrepared(ConfigurableEnvironment)ConfigurableEnvironment 准备完毕允许将其调整1.0contextPrepared(ConfigurableApplicationContext)ConfigurableApplicationContext 准备完毕允许将其调整1.0contextLoaded(ConfigurableApplicationContext)ConfigurableApplicationContext 已装载但仍未启动1.0started(ConfigurableApplicationContext)ConfigurableApplicationContext 已启动此时 Spring Bean 已初始化完成2.0running(ConfigurableApplicationContext)Spring 应用正在运行2.0failed(ConfigurableApplicationContext,Throwable)Spring 应用运行失败2.0我们在com.wno704.boot.listener下自定义一个SpringApplicationRunListener接口实现类HelloSpringApplicationRunListenerpublicclassHelloSpringApplicationRunListenerimplementsSpringApplicationRunListener{publicHelloSpringApplicationRunListener(SpringApplicationapplication,String[]args){}Overridepublicvoidstarting(){System.out.println(HelloApplicationRunListener starting......);}OverridepublicvoidenvironmentPrepared(ConfigurableEnvironmentenvironment){}OverridepublicvoidcontextPrepared(ConfigurableApplicationContextcontext){}OverridepublicvoidcontextLoaded(ConfigurableApplicationContextcontext){}Overridepublicvoidstarted(ConfigurableApplicationContextcontext){}Overridepublicvoidrunning(ConfigurableApplicationContextcontext){}Overridepublicvoidfailed(ConfigurableApplicationContextcontext,Throwableexception){}}通过这个实现类我们可以在Spring Boot应用刚启动的时候在控制台输出HelloApplicationRunListener starting…。因为其基于Spring的工厂方法来实现所以我们需要在spring.factories文件里配置这个实现类:org.springframework.boot.SpringApplicationRunListener\ com.wno704.boot.listener.HelloSpringApplicationRunListener启动Spring Boot应用便可以在控制台看到如下输出了3.2 创建 Environmentrun方法中的这行代码用于创建并配置当前SpringBoot应用将要使用的Environment包括配置要使用的PropertySource以及Profile我们已经在准备阶段里推断出了应用类型这里只要根据相应的应用类型来创建相应的应用环境即可类型和环境对应关系如下Web Reactive StandardReactiveWebEnvironmentWeb Servlet StandardServletEnvironment非 Web StandardEnvironment在prepareEnvironment方法中会执行listeners.environmentPrepared(environment);用于遍历调用所有SpringApplicationRunListener实现类的environmentPrepared()方法广播Environment准备完毕。3.3 是否打印Bannerrun方法中的这行代码会根据我们的配置来决定是否打印BannerBannerprintedBannerprintBanner(environment);3.4 创建Contextrun方法中的这行代码用于创建ApplicationContextcontextcreateApplicationContext();不同的环境对应不同的ApplicationContextWeb Reactive AnnotationConfigReactiveWebServerApplicationContextWeb Servlet AnnotationConfigServletWebServerApplicationContext非 Web AnnotationConfigApplicationContext3.5 装配Contextrun方法中的这行代码用于装配ContextprepareContext(context,environment,listeners,applicationArguments,printedBanner);3.6 方法prepareContext的源码如下所示:privatevoidprepareContext(ConfigurableApplicationContextcontext,ConfigurableEnvironmentenvironment,SpringApplicationRunListenerslisteners,ApplicationArgumentsapplicationArguments,BannerprintedBanner){context.setEnvironment(environment);postProcessApplicationContext(context);applyInitializers(context);listeners.contextPrepared(context);if(this.logStartupInfo){logStartupInfo(context.getParent()null);logStartupProfileInfo(context);}// Add boot specific singleton beansConfigurableListableBeanFactorybeanFactorycontext.getBeanFactory();beanFactory.registerSingleton(springApplicationArguments,applicationArguments);if(printedBanner!null){beanFactory.registerSingleton(springBootBanner,printedBanner);}if(beanFactoryinstanceofDefaultListableBeanFactory){((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}// Load the sourcesSetObjectsourcesgetAllSources();Assert.notEmpty(sources,Sources must not be empty);load(context,sources.toArray(newObject[0]));listeners.contextLoaded(context);}prepareContext方法开头为ApplicationContext加载了environment之后通过applyInitializers方法逐个执行ApplicationContextInitializer的initialize方法来进一步封装ApplicationContext并调用所有的SpringApplicationRunListener实现类的contextPrepared方法广播ApplicationContext已经准备完毕了。之后初始化IOC容器并调用SpringApplicationRunListener实现类的contextLoaded方法广播ApplicationContext加载完成这里就包括通过EnableAutoConfiguration导入的各种自动配置类。3.7 Refresh Contextrun方法中的这行代码用于初始化所有自动配置类并调用ApplicationContext的refresh方法refreshContext(context);3.8 广播应用已启动run方法中的这行代码用于广播Spring Boot应用已启动listeners.started(context);started方法会调用所有的SpringApplicationRunListener的finished方法广播SpringBoot应用已经成功启动。3.9 执行Runnerrun方法中的这行代码callRunners(context, applicationArguments);遍历所有ApplicationRunner和CommandLineRunner的实现类并执行其run方法。我们可以实现自己的ApplicationRunner或者CommandLineRunner来对Spring Boot的启动过程进行扩展。我们在com.wno704.boot下新建runner包然后创建一个ApplicationRunner的实现类HelloApplicationRunnerComponentpublicclassHelloApplicationRunnerimplementsApplicationRunner{Overridepublicvoidrun(ApplicationArgumentsargs){System.out.println(HelloApplicationRunner: hello spring boot);}}这里我们需要将HelloApplicationRunner使用Component注解标注让其注册到IOC容器中。然后再创建一个CommandLineRunner的实现类HelloCommandLineRunnerComponentpublicclassHelloCommandLineRunnerimplementsCommandLineRunner{Overridepublicvoidrun(String...args){System.out.println(HelloCommandLineRunner: hello spring boot);}}启动Spring Boot应用便可以在应用刚启动好后看到如下输出3.10 广播应用运行中run方法中的这行代码listeners.running(context);用于调用SpringApplicationRunListener的running方法广播Spring Boot应用正在运行中。当run方法运行出现异常时便会调用handleRunFailure方法来处理异常该方法里会通过listeners.failed(context, exception);来调用SpringApplicationRunListener的failed方法广播应用启动失败并将异常扩散出去。注意上面所有的广播事件都是使用Spring的应用事件广播器接口ApplicationEventMulticaster的实现类SimpleApplicationEventMulticaster来进行广播的。
返回列表