通常启动Spring Boot应用时调用SpringApplication
类的static run()
进行启动。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(SpringBootApplication.class, args);
}
}
其内部最终会转换为new一个SpringApplication对象,然后调用该对象的run方法,然后整个核心启动逻辑就由SpringApplication对象的run方法完成。
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
调用SpringApplication的静态run方法时,由于SpringApplication对象是在内部创建的,其会在启动Spring Boot时使用一些默认的配置。如果我们需要进行一些自定义配置,则可以自己手动的new一个SpringApplication对象,进行一些特殊配置后再调用SpringApplication对象的实例run方法。比如Spring Boot默认在启动的时候会输出Spring Boot的banner,其中包含了Spring Boot的版本信息,如果我们不希望输出该banner信息,则可以进行如下定制。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);
}
}
ApplicationEvent及其监听
SpringApplication在启动Spring Boot的过程中会发布以下ApplicationEvent,也可以参考SpringApplication的实例run方法的实现。
- ApplicationStartingEvent :会在进行其它操作之前发布
- ApplicationEnvironmentPreparedEvent : 接着是准备Environment,准备好了会发布该事件
- ApplicationPreparedEvent :接着会构造ApplicationContext,在构造好ApplicationContext之后,调用其
refresh()
方法之前会发布该事件 - ApplicationStartedEvent :在ApplicationContext进行refresh之后,调用ApplicationRunner和CommandLineRunner之前会发布该事件
- ApplicationReadyEvent :在Spring Boot应用启动完成之后,也就是在SpringApplication的
run()
调用马上结束之前会发布该事件 - ApplicationFailedEvent :在启动过程中出现异常时会发布该事件
从上述的事件发布过程可以看出,有些事件的发布是在ApplicationContext还没有准备好的情况下发布的,所以它们不能通过传统的定义ApplicationEvent实现类为bean容器中的一个bean的方式进行监听。SpringApplication接口为我们提供了专门的注册这些监听器的方法addListeners()
。事件监听器需要实现org.springframework.context.ApplicationListener
接口。以下定义了两个事件监听器,都只是简单的进行日志输出,然后在启动应用的时候通过addListeners()
添加了监听器,程序启动后会看到这两个监听器输出的日志信息。
@Slf4j
public class ApplicationStartingEventListener implements ApplicationListener<ApplicationStartingEvent> {
@Override
public void onApplicationEvent(ApplicationStartingEvent event) {
log.info("收到Spring Boot应用准备启动的事件[{}]", event);
}
}
@Slf4j
public class ApplicationReadyEventListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
log.info("收到Spring Boot应用启动完成的事件[{}]", event);
}
}
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.addListeners(new ApplicationStartingEventListener(), new ApplicationReadyEventListener());
app.run(args);
}
}
特别需要注意的是在添加监听器的时候不要调用SpringApplication的setListeners()
,而要调用其addListeners()
。因为在构造SpringApplication对象的时候构造方法中已经通过Spring Boot的Spring factory机制获取并注册了一些ApplicationListener(可以通过调用SpringApplication的getListeners()
获取到已经注册的ApplicationListener),使用setListeners()
会覆盖掉已经注册过的ApplicationListener。Spring Boot的Spring factory机制是指可以创建一个META-INF/spring.factories
文件,然后以接口类的全路径名称作为Key,以实现类的全路径名称作为Value,当有多个Value时以英文逗号分隔,当有多个Key时每个Key一行。它们会被SpringFactoriesLoader进行处理,可以通过它获取到定义的接口对应的实现类。Spring Boot中有很多扩展都是基于这个机制进行的。上面的定义的ApplicationListener实现类,如果需要使用Spring factory机制,则可以在spring.factories文件中添加如下内容:
org.springframework.context.ApplicationListener=com.elim.springboot.listener.ApplicationStartingEventListener,com.elim.springboot.listener.ApplicationReadyEventListener
当你觉得一行展示的内容太长了,期望折行展示时,可以在行末加上\
,这语法跟定义properties文件是一样的。实际上其内部也是按照properties文件进行解析的。
org.springframework.context.ApplicationListener=com.elim.springboot.listener.ApplicationStartingEventListener,\
com.elim.springboot.listener.ApplicationReadyEventListener
通过spring.factories文件定义了ApplicationListener后,我们的启动应用代码就可以改写为如下这种最简单的方式了。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
ApplicationContext的选择
默认情况下,当ClassPath下存在SpringMVC相关的Class时将使用org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
,当不存在SpringMVC相关的Class,而是存在SpringWebFlux相关的Class时将使用org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
,当两者都不存在时可以使用默认的org.springframework.context.annotation.AnnotationConfigApplicationContext
。可以通过其setWebApplicationType(WebApplicationType webApplicationType)
手动指定WebApplicationType,从而影响使用的ApplicationContext的选择,也可以直接通过setApplicationContextClass(Class applicationContextClass)
指定需要使用的ApplicationContext对应的Class。
访问命令行参数
调用SpringApplication的run()
时传递的参数通常来自于命令行的参数,SpringApplication内部在调用run()
时会把它们封装为一个ApplicationArguments对象,并且会把它定义为bean容器中的一个bean。如果在应用中需要访问命令行传递的参数,则可以通过注入ApplicationArguments对象,进行获取到对应的参数。命令行指定参数时有两种参数,一种是可选型参数、一种是非可选型参数,可选型参数以--
开头,需要赋值时可以加上=
,比如指定命令行参数为--debug --foo=bar abc
,则可选型参数为debug和foo,而非可选型参数为abc。如下代码就是基于该命令行参数的一个简单示例。
@Controller
public class SampleController {
@Autowired
private ApplicationArguments arguments;
/**
* 传递的命令行参数是--debug --foo=bar abc
* @param writer
* @throws Exception
*/
@GetMapping("sample/args")
public void arguments(PrintWriter writer) throws Exception {
writer.println("包含debug参数:" + arguments.containsOption("debug"));//true
writer.println("参数foo的值是:" + arguments.getOptionValues("foo"));//[bar]
writer.println("其它非选项性参数:" + arguments.getNonOptionArgs());//[abc]
writer.println("原始参数是:" + Arrays.toString(arguments.getSourceArgs()));//--debug, --foo=bar, abc
}
}
这种参数有别于在运行程序时通过
-Dkey=value
指定的虚拟机参数,通过-Dkey=value
指定的虚拟机参数可以通过System.getProperty("key")
获取到。命令行参数是对应程序运行主命令之后添加的参数,比如上面添加的那些参数的完整指令是java -jar app.jar --debug --foo=bar abc
。
ApplicationRunner和CommandLineRunner
前面在介绍事件监听器的时候已经介绍了,在Spring Boot应用启动成功后会在bean容器中寻找ApplicationRunner和CommandLineRunner类型的bean,调用它们的run()
。所以如果想在Spring Boot应用启动成功或做一些事情,则可以实现自己的ApplicationRunner或CommandLineRunner。它们的区别在于ApplicationRunner的run()
的入参是ApplicationArguments对象,而CommandLineRunner的run()
的入参是原始的参数数组。
@Component
@Slf4j
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("Spring Boot应用启动成功,携带的命令行参数是:{}", Arrays.toString(args.getSourceArgs()));
}
}
@Component
@Slf4j
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
log.info("Spring Boot应用已经启动成功了,携带的命令行参数是:{}", Arrays.toString(args));
}
}
其实前面介绍事件监听器的时候也提到了,通过实现ApplicationListener,监听ApplicationStartedEvent或ApplicationReadyEvent也可以在Spring Boot应用启动成功后做一些事情。它们的区别主要就在于ApplicationRunner和CommandLineRunner实现类是bean容器中的一个bean,可以注入其它bean,而且它们可以很方便的访问到命令行参数。
SpringApplicationBuilder
在构建SpringApplication对象时也可以通过SpringApplicationBuilder进行构建,通过它可以流式的进行配置,还可以指定子ApplicationContext。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
// SpringApplication.run(SpringBootApplication.class, args);
SpringApplication app = new SpringApplicationBuilder(Application.class)
.child(ChildConfig.class)
.bannerMode(Banner.Mode.OFF)
.build();
app.run(args);
}
}
关于SpringApplication的更多可定制的信息可以参考对应的API文档。
启用JMX管理
在application.properties文件中添加spring.application.admin.enabled=true
可以启用JMX管理,这会发布一个SpringApplicationAdminMXBean
类型的MBean。通过它的getProperty()
可以获取当前应用对应的启动JVM的一些系统属性或者是定义在application.properties中的一些属性的值,因为其底层对应的是当前Environment对象。通过其shutdown()
可以进行远程的关闭操作。