SpringApplication作为Spring Boot应用启动的入口,是大家做任何项目都绕不开的,因此我将其选为Spring Boot栏目的开篇。据我观察身边不少小伙伴在创建新应用时,很轻视这一块程序的编写,都是从网上复制黏贴,知其然而不知其所以然,然后急匆匆的去编写业务代码了。当出现问题,或需要一些特定效果的时候,就变得不知所措。所以,为了让我们能够更快捷、更优雅得编写应用入口程序达到想要的效果,熟悉并掌握SpringApplication提供的功能和相应的原理就变得十分重要了。
本篇首先会简单描述在Spring Boot出现之前Spring应用是如何初始化Spring应用上下文,从而对比以SpringApplication来引导应用的区别;其次会着重介绍SpringApplication提供的一些配置功能以及这些功能所适用的应用场景;最后会附上相关示例源码。话不多说,让我们开始吧!
Spring Boot之前的世界
在Spring Boot之前,Spring framework的使用者们通过调用相应方法加载xml配置文件的方式初始化应用上下文(或称为Ioc容器)。在注解(Annotation)尚不流行的年代里,此配置文件十分臃肿,除去一些properties配置、全局配置外还需要以xml的形式配置所有在上下文中的Bean Definition,规模大一些的项目,上千行的配置文件都是十分正常的,这大大提高了我们创建、维护项目的复杂度。对于一般应用,如桌面程序、命令行程序等,常用的初始化方式如下:
ApplicationContext context = new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
对于web应用,Spring通过web.xml中配置监听器的方式,在Web容器启动时,自动加载指定的配置文件以初始化Spring应用上下文(其实换汤不换药,只是将加载时机放到了web容器的生命周期钩子中):
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.WebAppRootListener
</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
此处通过context-param指定Spring应用上下文配置文件的路径。重点是下方的监听ContextLoaderListener,其继承自ContextLoader并实现了ServletContextListener(监听web容器生命周期事件)接口,核心作用就是在web容器启动时加载指定的配置文件以初始化Spring应用上下文。
Spring Boot闪亮登场
Spring Boot初始化应用容器的方式十分简单,在最简洁的情况下只需要一行代码即可(auto-configuration):
SpringApplication.run(Configuration.class, args);
对于只使用过Spring framework的人来说,这也太神奇、太简便、太不真实了吧!什么都没有告诉它,Spring是怎么就自动按照要求创建了应用上下文?这里就不得不提一下Spring Boot的核心思维—约定优于配置(Convention over Configuration)。这一软件设计范式可以说在Spring Boot的框架设计中体现的淋漓尽致,减少了大量不必要的配置,其体现在:
- 配置文件定位:默认加载classpath下application.properties文件作为上下文配置文件;
- 上下文创建:当依赖中出现Spring MVC,默认初始化AnnotationConfigServletWebServerApplicationContext,当出现WebFlux依赖则初始化为AnnotationConfigReactiveWebServerApplicationContext;
- Maven配置:默认打包为Jar包,maven目录结构等...
在通过约定简化了大量配置的同时,Spring Boot的SpringApplication和SpringApplicationBuilder所提供的功能也保留了配置的灵活性,下面让我们细细道来。
延迟初始化(Lazy Initialization)
普通模式下,Spring Beans会在应用上下文初始化时就自动载入。当项目规模较大时,大量的实例创建会导致项目启动时速度较慢,而延迟加载的提出就是来解决这个痛点的。通过SpringApplication可以方便得开启全局延迟加载:
app.setLazyInitialization(true);
开启后,上下文初始化时就不会加载任何自定义的业务Bean,这些Bean只有在使用时才会被实例化,这是你将会体验到飞一般的启动速度。但是!延迟加载带来的问题也是显而易见的:
- 响应速度慢:启动时实例化Beans所需的时间并没有消失,而是分摊到了首次请求的时候;
- 运行时异常难控制:由于实例化被延迟了,本该在应用启动时就暴露出来的问题被延迟到运行时才暴露。并且,由于首次请求的时间点也不确定,异常问题定位将会变得难度极大。
由于以上这些问题,除非遇到追求极致启动速度的特例,在一般的项目中几乎不会用到延迟初始化这个特性。因此Spring Boot里这个参数的缺省值也是false。
Banner设置
Spring Boot的缺省Banner如下图:
如果不想显示也可以通过SpringApplication进行设置,设置成OFF模式后启动应用时就会出出现Spring Banner了:
app.setBannerMode(Banner.Mode.OFF);
当然,你也可以定制个性化的Banner,如公司logo、个人logo等等。定制的方式是将Banner编写在banner.txt文件中,并将文件放置在classpath下:
再次运行应用就会看到属于你自己的banner!
banner.txt文件中还可以使用占位符来引入应用的一些版本信息等,可使用的占位符包括:
占位符 | 说明 |
${application.version} | 应用版本号,定义在MANIFEST.MF;如1.0 |
${application.formatted-version} | 格式化的应用版本号;如(v1.0) |
${spring-boot.version} | 所依赖的spring-boot版本号 |
${spring-boot.formatted-version} | 格式化的spring-boot版本号; |
${Ansi.NAME} | ANSI escape code的名称 |
${AnsiBackground.NAME} | |
${application.title} | 应用title |
定制你的应用
如前面提及的,spring boot通过约定大于配置的设计范式帮我们规避了大量配置工作,但是由于业务系统各有各的需求,总会出现一些不同于常规的要求。如前面的延迟加载、设置banner一样,通过SpringApplication可以方便得按需求定制应用行为。定制范围主要包括:
- Initializer的设置、获取
- 事件监听的设置、获取
- Environment绑定、设置
- 延迟加载设置
- 设置Web应用类型:none、reative(webflux)、servlet(spring mvc)
- 设置、获取上下文相关的ResourceLoader
- 设置、获取上下文相关的sources等
此外,spring boot还提供了一个工具类SpringApplicationBuilder,此类能够让我们以流式编程的方式来定制我们的应用,使得定制代码变得更优雅更可读:
SpringApplicationBuilder builder = new SpringApplicationBuilder();
builder.sources(DemoApplication.class)
.bannerMode(Banner.Mode.OFF).run();
除了使用SpringApplication和SpringApplicationBuilder来进行应用配置,spring boot同样支持配置外部化(externalize configuration)。外部化配置可以让我们在不修改源代码的情况下,通过修改外部配置文件来达到目的,前面提及的Enviroment设置、properties设置等都可以使用外部化配置实现。本篇不作展开,将会在下一篇中详细介绍外部化配置相关的内容。
应用事件及监听
spring boot应用具有哪些事件主要包括SpringApplication事件(应用级事件)、ApplicationContext事件(上下文级事件)、Web容器事件。应用级事件包括了应用完整生命周期的事件,其说明及触发的顺序如下:
- ApplicationStartingEvent:应用开始启动(什么都还没有做)
- ApplicationEnvironmentPreparedEvent:Environment准备完毕(即profile,properties载入成功),在上下文创建之前
- ApplicationContextInitializedEvent:应用上下文(ApplicationContext)准备完毕,在任何bean载入之前
- ApplicationPreparedEvent:在bean载入之后,上下文refresh之前
- ApplicationStartedEvent:在上下文refresh之后,任何ApplicationRunner或CommandLineRunner运行之前
- ApplicationReadyEvent:在ApplicationRunner或CommandLineRunner运行之后,这表示应用已经可以提供服务
- ApplicationFailedEvent:启动并出现异常情况时触发
上下文级事件:
- ContextRefreshedEvent:当ApplicationContext刷新完毕后触发
Web容器事件:
- ServletWebServerInitializedEvent:Web容器(如tomcat)执行完毕事件;springmvc专用
- ReactiveWebServerInitializedEvent:Web容器(如tomcat)执行完毕事件;webflux专用
注册事件处理方法有两种方式,一是通过SpringApplication的setListeners方法,此方式常用于上下文还未初始化完毕的应用级级事件(因为上下文都还未创建,spring bean是无法生效);另一种则是直接将处理类作为Spring Bean由上下文托管,常用于ApplicationPreparedEvent之后的应用级事件、上下文级事件或Web容器事件。
SpringApplication app = new SpringApplication(DemoApplication.class);
//通过SpringApplication注册listener;常用于context初始化完毕前的事件
app.addListeners(new StartingListener(),
new EnvironmentPreparedListener(),
new ContextInitializedListener(),
new PreparedListener()
);
app.run(args);
/**
* ApplicationStartedEvent
*
* 此事件及之后的应用级事件均可采用这种方式,因为context已创建
*/
@Component
public class StartedListener implements ApplicationListener<ApplicationStartedEvent> {
@Override
public void onApplicationEvent(ApplicationStartedEvent applicationStartingEvent) {
System.out.println("==============================StartedEvent");
}
}
spring boot应用的生命周期可以用事件流程图来表示,其中蓝底均为应用级事件、绿底为上下文级事件、黄底为web容器事件。同时,红字事件必须由SpringApplication注册(上下文创建之前),白字事件两种方式都可注册:
访问应用启动参数
如需要获取SpringApplication.run(...)中的启动参数,在所需使用的地方直接注入ApplicationArguments即可。ApplicationArguments既提供了启动参数的原始数据访问也提供了解析后的选项参数及非选项参数结果。
public class ArgBean {
//注入ApplicationArguments即可取得args
@Autowired
private ApplicationArguments args;
public void show(){
System.out.println("***********************启动参数原始值");
//启动参数原始值
Arrays.asList(args.getSourceArgs()).stream().forEach(item -> {
System.out.println(item);
});
System.out.println("***********************非选项启动参数");
//非选项启动参数
args.getNonOptionArgs().stream().forEach(item -> {
System.out.println(item);
});
System.out.println("***********************选项启动参数");
//非选项启动参数
args.getOptionNames().stream().forEach(item -> {
System.out.println(item + ":");
args.getOptionValues(item).stream().forEach(value -> {
System.out.println(value);
});
});
}
}
使用ApplicationRunner或CommandLineRunner
当有业务需要在SpringApplication启动后执行一次且仅一次特定代码的时候,可以通过创建类实现applicationRunner接口或commandLineRunner接口实现。
public class AppRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("****************************Runner执行");
}
}
总结
Spring Boot通过“约定优于配置”的设计范式为我们规避了大量的配置,同时其提供的SpringApplication和SpringApplicationBuilder所提供的方法(如:事件注册、链式配置等)又保留了配置的灵活性和便捷性。这使得使用Spring Boot来创建应用变得非常的高效和标准化。
示例源码:https://download.youkuaiyun.com/download/colin788/12403886
下期预告
外部化配置(Externalized Configuration)