开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情
接下来让我们进入SpringApplication的run方法
{
//1 创建StopWatch对象
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//2 设置Headless模式
configureHeadlessProperty();
//3 创建事件发布器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 将main方法的args参数封装到一个对象中
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//4 准备容器环境信息
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
//5.打印Banner
Banner printedBanner = printBanner(environment);
//6.创建IOC容器和Bean工厂
context = createApplicationContext();
//7.初始化异常报告器
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
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;
}
首先我们先看第一步
一、创建计时器
StopWatch是Spring框架的工具类,通过它可方便的对程序部分代码进行计时(ms级别),适用于同步单线程代码块
用法
public static void main(String[] args) throws InterruptedException {
StopWatchTest.testStopWatch();
}
public static void testStopWatch() throws InterruptedException {
StopWatch sw = new StopWatch("testWatch");
sw.start("TaskA");
// do something
Thread.sleep(100);
sw.stop();
sw.start("TaskB");
// do something
Thread.sleep(200);
sw.stop();
System.out.println("sw.prettyPrint()~~~~~~~~~~~~~~~~~");
System.out.println(sw.prettyPrint());
}
start开始记录,stop停止记录,然后通过StopWatch的prettyPrint方法,可直观的输出代码执行耗时,以及执行时间百分比
运行结果
运行结果:
sw.prettyPrint()~~~~~~~~~~~~~~~~~
StopWatch 'testWatch': running time (millis) = 308
-----------------------------------------
ms % Task name
-----------------------------------------
00104 034% TaskA
00204 066% TaskB
Springboot在使用计时器记录了容器启动前后的消费时间
二、设置Headless模式
Headless模式是系统的一种配置模式。在该模式下,系统缺少了显示设备、键盘或鼠标。我们的项目运行大多数是运行在linux操作系统下的,是没有显示器的,SpringBootSpringBoot其实是想设置该应用程序,即使没有检测到显示器,也允许其启动
private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
三、创建事件发布器
我们进入getRunListeners(args)方法中
private SpringApplicationRunListeners getRunListeners(String[] args) {
//使用Spring的Spi机制获取SpringApplicationRunListener的实现类
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
通过Spi机制获取META-INF/spring.factories下所有SpringApplicationRunListener实现类
SpringApplicationRunListener 接口的作用主要就是在Spring Boot 启动初始化的过程中可以通过SpringApplicationRunListener接口回调来让用户在启动的各个流程中可以加入自己的逻辑。 Spring Boot启动过程的关键事件(按照触发顺序)包括:
-
开始启动
-
Environment构建完成
-
ApplicationContext构建完成
-
ApplicationContext完成加载
-
ApplicationContext完成刷新并启动
-
启动完成
-
启动失败
下面是SpringApplicationRunListener接口定义:
public interface SpringApplicationRunListener {
// 在run()方法开始执行时,该方法就立即被调用,可用于在初始化最早期时做一些工作
void starting();
// 当environment构建完成,ApplicationContext创建之前,该方法被调用
void environmentPrepared(ConfigurableEnvironment environment);
// 当ApplicationContext构建完成时,该方法被调用
void contextPrepared(ConfigurableApplicationContext context);
// 在ApplicationContext完成加载,但没有被刷新前,该方法被调用
void contextLoaded(ConfigurableApplicationContext context);
// 在ApplicationContext刷新并启动后,CommandLineRunners和ApplicationRunner未被调用前,该方法被调用
void started(ConfigurableApplicationContext context);
// 在run()方法执行完成前该方法被调用
void running(ConfigurableApplicationContext context);
// 当应用运行出错时该方法被调用
void failed(ConfigurableApplicationContext context, Throwable exception);
}
Spirngboot中默认创建了一个实现类EventPublishingRunListener
SpringApplicationRunListeners的starting()方法中调用了所有的监听器的starting()方法;
void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting();
}
}
接下来进入EventPublishingRunListener
3.1、EventPublishingRunListener
EventPublishingRunListener是Springboot为我们提供的一套事件发布机制,他会在容器的启动的各个阶段发布对应的事件,我们可以实各个事件的监听器,拿到容器对应阶段的信息,完成自定义逻辑。
接下来看看EventPublishingRunListener是怎么完成事件的发布的
首先看这个类的构造器,这里的application和listener是在源码二启动时创建的。然后会创建一个Spring的事件发布器,并将所有监听器放入其中
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
接下来进入starting()方法
@Override
public void starting() {
//发布容器启动事件
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
第一步调用了SimpleApplicationEventMulticaster类的multicastEvent方法并且传入了ApplicationStartingEvent对象。
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
** }
}
紧接着遍历了每个事件是ApplicationStartingEvent的监听器,最后进行回调
@SuppressWarnings({"unchecked", "rawtypes"})
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || matchesClassCastMessage(msg, event.getClass().getName())) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
// -> let's suppress the exception and just log a debug message.
Log logger = LogFactory.getLog(getClass());
if (logger.isDebugEnabled()) {
logger.debug("Non-matching event type for listener: " + listener, ex);
}
}
else {
throw ex;
}**
}
}
所以这里就是发布了一个容器启动事件ApplicationStartedEvent
四、准备容器环境信息
获取IOC容器运行时环境信息
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
//4.1 创建运行时环境
ConfigurableEnvironment environment = getOrCreateEnvironment();
//4.2 配置运行时环境
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
//4.3 发布容器环境准备就绪事件
listeners.environmentPrepared(environment);
//4.4 环境与应用绑定
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
4.1 创建运行时环境
根据不同的容器信息创建不同的环境载体
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
我们一般用Tomcat容器这里我们看看StandardServletEnvironment
StandardServletEnvironment继承了AbstractEnvironment所以在初始化构造方法时候会默认调用父类构造器
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
}
加载自定义数据源,这里在数据源中加入了Sevlet配置和上下文信息,并且调用了父类StandardEnvironment的加载数据源方法
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
super.customizePropertySources(propertySources);
}
这里将系统的配置和环境信息放入到数据源中
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
4.2配置环境信息
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
//获取装换器
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
//设置转换器
environment.setConversionService((ConfigurableConversionService) conversionService);
}
//配置PropertySource
configurePropertySources(environment, args);
//配置Profiles
configureProfiles(environment, args);
}
SpringBoot会默认创建ApplicationConversionService转换器
public static ConversionService getSharedInstance() {
ApplicationConversionService sharedInstance = ApplicationConversionService.sharedInstance;
if (sharedInstance == null) {
synchronized (ApplicationConversionService.class) {
sharedInstance = ApplicationConversionService.sharedInstance;
if (sharedInstance == null) {
sharedInstance = new ApplicationConversionService();
ApplicationConversionService.sharedInstance = sharedInstance;
}
}
}
return sharedInstance;
}
ApplicationConversionService在创建时候会添加各种类型转换器,例如String,Number等
最后会将刚刚获取到的系统配置和环境信息放入配置源中,然后设置PropertySources和Profiles
4.3 发布容器环境准备就绪事件
接下来会发布容器环境已经好的容器事件ApplicationEnvironmentPreparedEvent
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
这里ConfigFileApplicationListener监听器接收到ApplicationEnvironmentPreparedEvent会将配置文件中的信息放入数据源中
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
加载配置文件
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
void load() {
FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
(defaultProperties) -> {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
initializeProfiles();
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (isDefaultProfile(profile)) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
applyActiveProfiles(defaultProperties);
});
}
定义在这些目录下的配置文件下都会被加载到环境当中
- classpath:/
- classpath:/config/
- file:./
- file:./config/*/
- file:./config/
private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter,
DocumentConsumer consumer) {
Resource[] resources = getResources(location);
for (Resource resource : resources) {
try {
if (resource == null || !resource.exists()) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped missing config ", location, resource,
profile);
this.logger.trace(description);
}
continue;
}
if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped empty config extension ", location,
resource, profile);
this.logger.trace(description);
}
continue;
}
if (resource.isFile() && hasHiddenPathElement(resource)) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped location with hidden path element ",
location, resource, profile);
this.logger.trace(description);
}
continue;
}
String name = "applicationConfig: [" + getLocationName(location, resource) + "]";
List<Document> documents = loadDocuments(loader, name, resource);
if (CollectionUtils.isEmpty(documents)) {
if (this.logger.isTraceEnabled()) {
StringBuilder description = getDescription("Skipped unloaded config ", location, resource,
profile);
this.logger.trace(description);
}
continue;
}
List<Document> loaded = new ArrayList<>();
for (Document document : documents) {
if (filter.match(document)) {
addActiveProfiles(document.getActiveProfiles());
addIncludedProfiles(document.getIncludeProfiles());
loaded.add(document);
}
}
Collections.reverse(loaded);
if (!loaded.isEmpty()) {
loaded.forEach((document) -> consumer.accept(profile, document));
if (this.logger.isDebugEnabled()) {
StringBuilder description = getDescription("Loaded config file ", location, resource,
profile);
this.logger.debug(description);
}
}
}
catch (Exception ex) {
StringBuilder description = getDescription("Failed to load property source from ", location,
resource, profile);
throw new IllegalStateException(description.toString(), ex);
}
}
}
4.4 绑定容器环境
就是把配置内容绑定到指定的属性配置类中
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
try {
Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
}
catch (Exception ex) {
throw new IllegalStateException("Cannot bind to SpringApplication", ex);
}
}
五、打印Banner
Banner就是我们启动SpringBoot容器时候控制台打印的图标
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
//5.1获取资源加载器
ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
: new DefaultResourceLoader(null);
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
//5.2输出Banner
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
5.1ResourceLoader
用于加载资源(例如类路径或文件系统资源)的策略接口
主要就是借助URL类来加载资源
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
5.2 输出Banner
打印Banner之前会先获取Banner
Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
Banner banner = getBanner(environment);
banner.printBanner(environment, sourceClass, out);
return new PrintedBanner(banner, sourceClass);
}
获取Banner
private Banner getBanner(Environment environment) {
Banners banners = new Banners();
//5.2.1获取图片
banners.addIfNotNull(getImageBanner(environment));
//5.2.2获取文本
banners.addIfNotNull(getTextBanner(environment));
if (banners.hasAtLeastOneBanner()) {
return banners;
}
if (this.fallbackBanner != null) {
return this.fallbackBanner;
}
//5.2.3返回默认的Banner
return DEFAULT_BANNER;
}
5.2.1获取图片Banner
获取资源路径下Banner图片
private Banner getImageBanner(Environment environment) {
//获取Banner图片地址spring.banner.image.location
String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY);
if (StringUtils.hasLength(location)) {
Resource resource = this.resourceLoader.getResource(location);
return resource.exists() ? new ImageBanner(resource) : null;
}
//获取Resouces下,以banner.gif, banner.jpg, banner.png的图片
for (String ext : IMAGE_EXTENSION) {
Resource resource = this.resourceLoader.getResource("banner." + ext);
if (resource.exists()) {
return new ImageBanner(resource);
}
}
return null;
}
5.2.2获取文本Banner
获取资源路径下Banner文本
private Banner getTextBanner(Environment environment) {
//获取文本Banner地址spring.banner.image.location
String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
//加载文本Banner(默认加载Resources下的banner.txt)
Resource resource = this.resourceLoader.getResource(location);
if (resource.exists()) {
return new ResourceBanner(resource);
}
return null;
}
5.2.3获取默认Banner
SpringBoot的默认Banner打印版本信息和启动类
public void printBanner(Environment environment, Class<?> sourceClass, PrintStream printStream) {
String[] var4 = BANNER;
int var5 = var4.length;
for(int var6 = 0; var6 < var5; ++var6) {
String line = var4[var6];
printStream.println(line);
}
String version = SpringBootVersion.getVersion();
version = version != null ? " (v" + version + ")" : "";
StringBuilder padding = new StringBuilder();
while(padding.length() < 42 - (version.length() + " :: Spring Boot :: ".length())) {
padding.append(" ");
}
printStream.println(AnsiOutput.toString(new Object[]{AnsiColor.GREEN, " :: Spring Boot :: ", AnsiColor.DEFAULT, padding.toString(), AnsiStyle.FAINT, version}));
printStream.println();
}
六、创建IOC容器和Bean工厂
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."+ "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."+"web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."+"boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
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);
}
SpringBoot都是基于注解创建的Ioc容器
(如果是非Web环境,创建的 ApplicationContext 与常规用 SpringFramework 时使用的注解驱动IOC容器一致)
Tomcat环境下会创建AnnotationConfigServletWebServerApplicationContext作为IOC容器,需要注意的是Bean工厂(DefaultListableBeanFactory)在GenericApplicationContext父类构造器中就会被创建
public GenericApplicationContext() {
this.customClassLoader = false;
this.refreshed = new AtomicBoolean();
this.beanFactory = new DefaultListableBeanFactory();
}
并且BeanDefinition读取器和扫描器都会被创建出来
public AnnotationConfigServletWebServerApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
七、初始化异常报告器
这里会创建SpirngBoot出现异常分析器,用于收集错误信息,向用户报告错误原因。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
总结
-
创建了事件发布器SpringApplicationRunListeners
-
创建了运行环境并向其中那个添加了系统配置和环境信息
-
发布了两个事件
- ApplicationStartingEvent(容器启动事件)
- ApplicationEnvironmentPreparedEvent (环境准备事件)
-
创建IOC容器AnnotationConfigServletWebServerApplicationContext并且创建了默认的Bean工厂DefaultListableBeanFactory,AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner
ConfigFileApplicationListener监听器在在接受ApplicationEnvironmentPreparedEvent事件后通过执行器将Reources下的配置文件里的配置信息添加到运行环境中