文章目录
自从 Spring Boot问世以来,已经有越来越多的项目完成了从 SpringMVC迁移到 Spring Boot的迁移过程。本系列将从源码(spring boot 2.1.3.RELEASE)的角度来聊聊 Spring Boot是如何让我们的开发体验变得如此效率。
主函数
package com.boot.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BootApplication {
public static void main(String[] args) {
SpringApplication.run(BootApplication.class,args);
}
}
以上这段代码,就已经完成了一个最简单的Spring Boot应用(虽然没有任何功能,但已经能够正常启动,并可以通过Web浏览器访问了)。越是简洁的代码,其背后往往隐藏的是越复杂的实现。
在这段代码中,我们可以很清晰的看到,用Spring Boot构建Web应用,只需要2个步骤
- 创建主类,并构建主函数,用SpringApplication提供的静态方法run()运行项目。
- 在主类上,使用**@SpringBootApplication**注解
接下来,我们来看看,这两个类的特别之处吧。
@SpringBootApplication
@SpringBootApplication.java
package org.springframework.boot.autoconfigure;
//import ...
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
//...
}
从源码中,我们可以看到,该注解的作用实际上就是耦合**@SpringBootConfiguration**,@EnableAutoConfiguration,@ComponentScan这几个注解的功能。
@SpringBootConfiguration.java
package org.springframework.boot;
//import ...
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
该注解的作用同**@Configuration**。
@EnableAutoConfiguration.java
package org.springframework.boot.autoconfigure;
//import ...
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
该注解是用来标识项目中使用Spring的自动装载功能。这也是Spring Boot项目开发简单的关键注解。
@ComponentScan.java
该注解,实际上也没啥可提的,其实现的功能就是自动扫描。值得提一下的就是,可以通过配置basePackages或者basePackageClasses来设置需要扫描的包目录**(在Spring Boot中,默认扫描主类所在的包下的同级类以及子包)**。
好了,到这里,注解这块,我们就聊得差不多了,下面聊聊SpringApplication吧,它是整个项目启动的核心,可以说没有它,Spring Boot也就无从谈起了。
SpringApplication
上面已经提到,Spring Boot项目启动,实际上是托管于SpringApplication.java这个类的静态方法run(),下面我们来看看**run()**方法的实现吧
实现代码
public class SpringApplication {
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch(); //计时工具
stopWatch.start(); //开始计时
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//设置java.awt.headless,默认为true(没有图形化界面)
configureHeadlessProperty();
//Key 1 : 获取启动过程监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(); //执行开始启动事件
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//KEY 2: 初始化Environment
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//设置spring.beaninfo.ignore,默认为true
configureIgnoreBeanInfo(environment);
//打印Banner图
Banner printedBanner = printBanner(environment);
//KEY 3: 创建ApplicationContext
context = createApplicationContext();
//获取异常记录器
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//KEY 4 : 初始化ApplicationContext
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//KEY 5 : 刷新ApplicationContext
refreshContext(context);
//刷新ApplicationContext后,回调事件(默认为空方法,可以自定义实现)
afterRefresh(context, applicationArguments);
stopWatch.stop(); //停止计时
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//执行启动后事件
listeners.started(context);
//Key 6 : 启动后置处理
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
//执行运行中事件
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
/**
* 静态方法run入口
* @param primarySource 主类(main方法所在的类)
* @param args 启动参数
*/
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
//调用重载方法run()
return run(new Class<?>[] { primarySource }, args);
}
/**
* @param primarySource 主类(main方法所在的类)
* @param args 启动参数
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
//创建SpringApplication实例,并调用run()方法
return new SpringApplication(primarySources).run(args);
}
}
从以上源码,可以看到SpringApplication.java在启动项目的过程中,主要有6个核心操作,下面将针对每一点做扩展解读
KEY 1 : getRunListeners()
实现代码
public class SpringApplication {
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
//获取ClassLoader
ClassLoader classLoader = getClassLoader();
//获取指定类型的实现类名(不能通过注册为Spring Bean,需通过META-INF/spring.factories配置)
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//创建指定类型的实现类实例
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
//对实例排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
}
从源码中,可以看出该方法的作用就是返回SpringApplicationRunListeners的实例,而SpringApplicationRunListeners里存储的是SpringApplicationRunListener的所有实例
SpringApplicationRunListeners.java
class SpringApplicationRunListeners {
private final Log log;
private final List<SpringApplicationRunListener> listeners;
SpringApplicationRunListeners(Log log,
Collection<? extends SpringApplicationRunListener> listeners) {
this.log = log;
this.listeners = new ArrayList<>(listeners);
}
public void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting();
}
}
public void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
public void contextPrepared(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.contextPrepared(context);
}
}
public void contextLoaded(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.contextLoaded(context);
}
}
public void started(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.started(context);
}
}
public void running(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.running(context);
}
}
public 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 + ")");
}
}
}
}
SpringApplicationRunnerListener.java
public interface SpringApplicationRunListener {
//启动事件
void starting();
//Environment初始化完成回调事件
void environmentPrepared(ConfigurableEnvironment environment);
//ApplicationContext初始化完成回调事件
void contextPrepared(ConfigurableApplicationContext context);
//ApplicationContext加载后回调事件
void contextLoaded(ConfigurableApplicationContext context);
//启动完成事件
void started(ConfigurableApplicationContext context);
//运行中事件
void running(ConfigurableApplicationContext context);
//启动失败事件
void failed(ConfigurableApplicationContext context, Throwable exception);
}
从以上源码中,可以看出SpringApplicationRunnerListener监听的是整个Spring Boot启动过程。至于实际运用场景,则需要具体问题,具体分析了。而SpringApplicationRunnerListeners的作用,则相当于一个拦截器,将所有的监听请求截获,并转发给所有以注册的监听器里。
KEY 2 :prepareEnvironment()
实现代码
public class SpringApplication {
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
//根据web应用类型不同,创建不同的Environment对象
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService
.getSharedInstance();
environment.setConversionService(
(ConfigurableConversionService) conversionService);
}
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
}
可以看出,这里初始化Environment对象,实际上,包括四个步骤:
- 根据Web应用类型,创建不同Environment实例(正常来说,创建的是**StandardServletEnvironment实例)。
- 配置Property Sources
- 配置Profiles
- 执行Environment对象初始化完成回调事件
这里源码必将直观,就不做展开讲述了。
KEY 3 :createApplicationContext()
实现代码
public class SpringApplication {
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:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
}
这里的话,代码也比较直观,就是根据Web应用类型,创建不同的ApplicationContext实例(正常来说,创建的是AnnotationConfigServletWebServerApplicationContext实例)。
KEY 4 :prepareContext()
实现代码
public class SpringApplication {
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//关联ApplicationContext和Environment实例
context.setEnvironment(environment);
//在ApplicationContext中设置
//Bean名称生成器(如果存在),资源加载器(如果存在)以及类型转换服务
postProcessApplicationContext(context);
//调用所有初始化器
applyInitializers(context);
//执行ApplicationContext初始化完成事件
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
//添加Spring Boot中的特殊单例Bean:springApplicationArguments 以及 springBootBanner
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
//设置是否允许Bean重载,默认true
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
//加载Sources,对于该项目而言,只包含BootApplication这一个类
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载动作 - 构造BeanDefinitionLoader并完成所有sources集合中类的Bean定义的加载
load(context, sources.toArray(new Object[0]));
//执行ApplicationContext加载后回调事件
listeners.contextLoaded(context);
}
/**
* 在ApplicationContext中设置
* Bean名称生成器(如果存在),资源加载器(如果存在)以及类型转换服务
*/
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
if (this.beanNameGenerator != null) {
context.getBeanFactory().registerSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
this.beanNameGenerator);
}
if (this.resourceLoader != null) {
if (context instanceof GenericApplicationContext) {
((GenericApplicationContext) context)
.setResourceLoader(this.resourceLoader);
}
if (context instanceof DefaultResourceLoader) {
((DefaultResourceLoader) context)
.setClassLoader(this.resourceLoader.getClassLoader());
}
}
if (this.addConversionService) {
context.getBeanFactory().setConversionService(
ApplicationConversionService.getSharedInstance());
}
}
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
}
可以看出,这段代码的功能主要还是集中在为后续加载Spring容器做的准备工作上。
KEY 5 :refreshContext()
实现代码
public class SpringApplication {
private void refreshContext(ConfigurableApplicationContext context) {
//刷新ApplicationContext
refresh(context);
// 注册一个关闭容器时的钩子函数
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
}
这段代码主要就是完成对ApplicationContext实例的刷新操作,这里暂时不做展开,先了解即可。
KEY 6 :callRunners()
实现代码
public class SpringApplication {
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);
}
}
}
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
try {
(runner).run(args);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
}
}
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
try {
(runner).run(args.getSourceArgs());
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
}
}
}
这里的功能,就是依次调用各种Runner来完成初始化过程中的一些后置处理,这里的处理可以包括但不全包括:Redis键值清理,读取配置文件,数据库连接等操作。
然后,自定义Runner仅能通过实现ApplicationRunner,CommandLineRunner其中之一来实现。这两个接口的区别不大,只是入参不同而已,因此根据需要选择相应的接口实现即可。
ApplicationRunner.java
@FunctionalInterface
public interface ApplicationRunner {
/**
* Callback used to run the bean.
* @param args incoming application arguments
* @throws Exception on error
*/
void run(ApplicationArguments args) throws Exception;
}
CommandLineRunner.java
@FunctionalInterface
public interface CommandLineRunner {
/**
* Callback used to run the bean.
* @param args incoming main method arguments
* @throws Exception on error
*/
void run(String... args) throws Exception;
}