SpringBoot启动方式

SpringBoot启动方式

springboot的启动经过了一些一系列的处理,我们先看看整体过程的流程图
在这里插入图片描述
SpringBoot的启动方式

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.6.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.spring</groupId><artifactId>springBoot-demo1</artifactId><version>1.0-SNAPSHOT</version><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies>

第一种启动方式 @EnableAutoConfiguration

@EnableAutoConfiguration 作用

开启自动装配,帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot,并创建对应配置类的Bean,并把该Bean实体交给IoC容器进行管理。

新建文件 IndexController.java

package com.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@EnableAutoConfiguration
@RestController
public class IndexController {// 访问路径   http://localhost:8080/index@RequestMapping("/index")public String index(){System.out.println("我进来了");return "index controller";}public static void main(String[] args) {// 启动springbootSpringApplication.run(IndexController.class,args);}
}

第二种启动方式 @ComponentScan

@ComponentScan() 注解作用

根据定义的扫描路径,将符合规则的类加载到spring容器中,比如在类中加入了以下注解 @Controller、@Service、@Mapper 、@Component、@Configuration 等等;

package com.spring.controller;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;@EnableAutoConfiguration  // 开启自动装配
@ComponentScan("com.spring.controller")
public class App {public static void main(String[] args) {// 启动springbootSpringApplication.run(App.class,args);}
}

控制层 OrderController.java

package com.spring.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {@RequestMapping("/order")public String order(){System.out.println("我进来了 order controller 的 index 方法");return "order controller";}
}

第三种方式 @SpringBootApplication

@SpringBootApplication 注解作用
标注这是一个springboot的应用,被标注的类是一个主程序, SpringApplication.run(App.class, args);传入的类App.class必须是被@SpringBootApplication标注的类。

@SpringBootApplication是一个组合注解,组合了其他相关的注解,点进去注解后我们可以看到,这个注解集成了以上2种启动方式的注解;在这里的 @ComponentScan() 注解有一堆东西,它的作用是 将主配置类所在包及其下面所有后代包的所有注解扫描;

SpringBootApplication 使用起来更加简单,只需要一个注解即可完成,

package com.spring.controller;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class App {public static void main(String[] args) {// 启动springbootSpringApplication.run(App.class,args);}
}

SpringBoot启动流程

运行main函数,调用run方法
//分为两步// 1、new SpringApplication(primarySources)// 2、run(args)

创建SpringApplication对象

1、启动SpringBoot启动类SpringbootdemoApplication中的main方法。

@SpringBootApplication
public class SpringbootdemoApplication {public static void main(String[] args) {SpringApplication.run(SpringbootdemoApplication.class, args);}
}

2、调用SpringApplication.run(SpringbootdemoApplication.class, args),该方法是一个静态方法。

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class<?>[] { primarySource }, args);
}

3、继续调用SpringApplication内部的run方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {return new SpringApplication(primarySources).run(args);
}

且构建了一个SpringApplication对象,应用程序将从指定的主要来源加载Bean

public SpringApplication(Class<?>... primarySources) {this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {//resourceLoader赋值为Null// 初始化类加载器 this.resourceLoader = resourceLoader;//primarySources不为空,继续向下执行。为空抛异常// Assert 断言非空,若传入的class参数为null则打印异常并退出初始化Assert.notNull(primarySources, "PrimarySources must not be null");//将SpringbootdemoApplication(启动类)赋值给primarySources ,去重转换成primarySources对象// 传进来的class对象变成一个集合对象,只是做了一个格式转换,基本信息没有变化this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));//Web容器类型推断//从classpath类路径推断Web应用类型,有三种Web应用类型,分别是//NONE: 该应用程序不应作为 Web 应用程序运行,也不应启动嵌入式 Web 服务器//SERVLET: 该应用程序应作为基于 servlet 的 Web 应用程序运行,并应启动嵌入式 servlet Web 服务器。//REACTIVE: 该应用程序应作为响应式 Web 应用程序运行,并应启动嵌入式响应式 Web 服务器// 返回NONE//<dependency>//     <groupId>org.springframework.boot</groupId>//    <artifactId>spring-boot-starter</artifactId>//   </dependency>// 返回SERVLET//<dependency>//     <groupId>org.springframework.boot</groupId>//    <artifactId>spring-boot-starter-web</artifactId>//   </dependency>//<dependency>//     <groupId>org.springframework.boot</groupId>//    <artifactId>spring-boot-starter-webflux</artifactId>//   </dependency>// 简单点说是:要不要加载web容器,或者说加载什么类型的容器this.webApplicationType = WebApplicationType.deduceFromClasspath();//初始化ApplicationContextInitializer集合// 获取 Spring 工厂实例 -> 容器上下文相关的初始化setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));//初始化ApplicationListener// 获取 Spring 工厂实例 -> 设置应用程序监听器setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//获取StackTraceElement数组遍历,通过反射获取堆栈中有main方法的类。// 推导出主应用程序类,即从当前的栈信息中寻找main所在主类:包名.类名this.mainApplicationClass = deduceMainApplicationClass();
}

在SpringApplication的构造方法内,首先会通过 WebApplicationType.deduceFromClasspath(); 方法判断当前应用程序的容器,默认使用的是Servlet 容器,除了servlet之外,还有NONE 和 REACTIVE (响应式编程);

this.webApplicationType = WebApplicationType.deduceFromClasspath();

Spring boot依据Class path来推断web应用类型

  • reactive

路径包含org.springframework.web.reactive.DispatcherHandler,并且不包含org.springframework.web.servlet.DispatcherServlet和org.glassfish.jersey.servlet.ServletContainer

  • none

不是reactive,并且路径中没有javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext

  • servlet

剩余的部分都是servlet

NONE:不是一个 web 应用,不需要启动内置的 web 服务器,所以应用启动运行后就自动关闭了,并没有启动内置的 web 服务器,也没有监听任何端口。

SERVLET:基于 servlet 的 web 应用,需要启动一个内置的 servlet 服务器;通过启动日志我们可以看到这里启动了内置的 Tomcat Servlet 服务器,监听了 8080 端口,应用程序并不会像 None 类型一样,启动后就自动关闭。

REACTIVE:一个 reactive 的 web 应用,需要启动一个内置的 reactive 服务器;通过启动日志我们可以看到,这里启动了内置的 Netty 服务器,并监听在 8080 端口上

/** Copyright 2012-2019 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package org.springframework.boot;import org.springframework.util.ClassUtils;/*** An enumeration of possible types of web application.** @author Andy Wilkinson* @author Brian Clozel* @since 2.0.0*/
public enum WebApplicationType {/*** The application should not run as a web application and should not start an* embedded web server.*/NONE,/*** The application should run as a servlet-based web application and should start an* embedded servlet web server.*/SERVLET,/*** The application should run as a reactive web application and should start an* embedded reactive web server.*/REACTIVE;private static final String[] SERVLET_INDICATOR_CLASSES = {"javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext"};private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";/*** deduceFromClasspath* 依次循环遍历当前应用中是否存在相关的类来判断最终应用的启动类型** @return*/static WebApplicationType deduceFromClasspath() {/*** REACTIVE:响应式WEB项目* 若启动类型为REACTIVE,* 则类路径下存在 org.springframework.web.reactive.DispatcherHandler 类* 并且不存在 org.springframework.web.servlet.DispatcherServlet 和 org.glassfish.jersey.servlet.ServletContainer* 两者指的是SpringMVC/Tomcat和jersey容器*/if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {return WebApplicationType.REACTIVE;}/*** NONE:非WEB项目,就是一个最简单的Springboot应用* 若启动类型为NONE* 则类路径下 javax.servlet.Servlet 和org.springframework.web.context.ConfigurableWebApplicationContext都不存在*/for (String className : SERVLET_INDICATOR_CLASSES) {if (!ClassUtils.isPresent(className, null)) {return WebApplicationType.NONE;}}/*** SERVLET:SERVLET WEB 项目* 若启动类型为Servlet,则必须有SERVLET_INDICATOR_CLASSES中的javax.servlet.Servlet* 和org.springframework.web.context.ConfigurableWebApplicationContext*/return WebApplicationType.SERVLET;}static WebApplicationType deduceFromApplicationContext(Class<?> applicationContextClass) {if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {return WebApplicationType.SERVLET;}if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {return WebApplicationType.REACTIVE;}return WebApplicationType.NONE;}private static boolean isAssignable(String target, Class<?> type) {try {return ClassUtils.resolveClassName(target, null).isAssignableFrom(type);} catch (Throwable ex) {return false;}}}

getSpringFactoriesInstances(Class type, Class<?>[] parameterTypes, Object… args)中首先获取ClassLoader ,通过SpringFactoriesLoader机制,根据ClassLoader从类路径jar包中加载META-INF/spring.factories下的所有工厂类及其实现类 。

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {return getSpringFactoriesInstances(type, new Class<?>[] {});}private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {//获取ClassLoader ClassLoader classLoader = Thread.currentThread().getContextClassLoader();// 使用SpringFactoriesLoader机制加载出工厂名,并放入Set集合中确保其唯一性。但是在META-INF/spring.factories中并无BootstrapRegistryInitializer所以此处的names的size为0。Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));//创建Spring工厂实例(但因为上述names的size为0,所以对于BootstrapRegistryInitializer来说它并不会创建SpringFactory实例)List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);//通过AnnotationAwareOrderComparator对Spring工厂实例排序AnnotationAwareOrderComparator.sort(instances);return instances;
}

这里加载的初始化器是springboot自带初始化器,从从 META-INF/spring.factories 配置文件中加载的,那么这个文件在哪呢?自带有2个,分别在源码的jar包的 spring-boot-autoconfigure 项目 和 spring-boot 项目里面各有一个

在这里插入图片描述
spring.factories文件里面,看到开头是 org.springframework.context.ApplicationContextInitializer 接口就是初始化器了 ,
在这里插入图片描述
当然,我们也可以自己实现一个自定义的初始化器:实现 ApplicationContextInitializer接口既可

MyApplicationContextInitializer.java

package com.spring.application;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
/** * 自定义的初始化器 */
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) { System.out.println("我是初始化的 MyApplicationContextInitializer...");}
}

在resources目录下添加 META-INF/spring.factories 配置文件,内容如下,将自定义的初始化器注册进去;

org.springframework.context.ApplicationContextInitializer=\
com.spring.application.MyApplicationContextInitializer

在这里插入图片描述
启动springboot后,就可以看到控制台打印的内容了,在这里我们可以很直观的看到它的执行顺序,是在打印banner的后面执行的;
在这里插入图片描述
加载监听器也是从 META-INF/spring.factories 配置文件中加载的,与初始化不同的是,监听器加载的是实现了 ApplicationListener 接口的类

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

在这里插入图片描述
自定义监听器也跟初始化器一样

org.springframework.context.ApplicationListener=\
com.maweiqi.MyListener
import org.springframework.boot.context.event.ApplicationStartingEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;//public class MyListener implements ApplicationListener {
//    @Override
//    public void onApplicationEvent(ApplicationEvent event) {
//        System.out.println("-----------");
//        System.out.println(event.getTimestamp());
//        System.out.println(event.getSource());
//        System.out.println(event.getClass());
//    }
//}public class MyListener implements ApplicationListener<ApplicationStartingEvent> {@Overridepublic void onApplicationEvent(ApplicationStartingEvent event) {System.out.println("-----可以干预不同的监听器------");System.out.println(event.getTimestamp());System.out.println(event.getSource());System.out.println(event.getClass());}
}

deduceMainApplicationClass(); 这个方法仅仅是找到main方法所在的类,为后面的扫包作准备,deduce是推断的意思,所以准确地说,这个方法作用是推断出主方法所在的类;

this.mainApplicationClass = deduceMainApplicationClass();

执行Run方法

我们看启动springboot最核心的逻辑run方法

public ConfigurableApplicationContext run(String... args) {// new 一个StopWatch用于统计run启动过程花了多少时间StopWatch stopWatch = new StopWatch();// 开始计时stopWatch.start();//关键类,它是任何spring上下文的接口, 所以可以接收任何ApplicationContext实现ConfigurableApplicationContext context = null;// exceptionReporters集合用来存储异常报告器,用来报告SpringBoot启动过程的异常Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();/*** 设置jdk系统属性* headless直译就是无头模式,* headless模式的意思就是明确Springboot要在无鼠键支持的环境中运行,一般程序也都跑在Linux之类的服务器上,无鼠键支持,这里默认值是     true;*/configureHeadlessProperty();// 【1】从spring.factories配置文件中加载到EventPublishingRunListener对象并赋值给SpringApplicationRunListeners// EventPublishingRunListener对象主要用来发射SpringBoot启动过程中内置的一些生命周期事件,标志每个不同启动阶段SpringApplicationRunListeners listeners = getRunListeners(args);// 启动SpringApplicationRunListener的监听,表示SpringApplication开始启动。// 》》》》》发射【ApplicationStartingEvent】事件listeners.starting();try {// 根据命令行参数 实例化一个ApplicationArguments// 封装命令行参数,也就是在命令行下启动应用带的参数,如--server.port=9000 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 【2】准备环境变量,包括系统变量,环境变量,命令行参数,默认变量,servlet相关配置变量,随机值,// JNDI属性值,以及配置文件(比如application.properties)等,注意这些环境变量是有优先级的// 》》》》》发射【ApplicationEnvironmentPreparedEvent】事件ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);//忽略beaninfo的bean      configureIgnoreBeanInfo(environment);// 【3】控制台打印SpringBoot的bannner标志Banner printedBanner = printBanner(environment);//根据webApplicationType创建Spring上下文// 【4】根据不同类型创建不同类型的spring applicationcontext容器// 因为这里是servlet环境,所以创建的是AnnotationConfigServletWebServerApplicationContext容器对象context = createApplicationContext();// 【5】从spring.factories配置文件中加载异常报告期实例,这里加载的是FailureAnalyzers// 注意FailureAnalyzers的构造器要传入ConfigurableApplicationContext,因为要从context中获取beanFactory和environmentexceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);// 【6】为刚创建的AnnotationConfigServletWebServerApplicationContext容器对象做一些初始化工作,准备一些容器属性值等// 1)为AnnotationConfigServletWebServerApplicationContext的属性AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner设置environgment属性// 2)根据情况对ApplicationContext应用一些相关的后置处理,比如设置resourceLoader属性等// 3)在容器刷新前调用各个ApplicationContextInitializer的初始化方法,ApplicationContextInitializer是在构建SpringApplication对象时从spring.factories中加载的// 4)》》》》》发射【ApplicationContextInitializedEvent】事件,标志context容器被创建且已准备好// 5)从context容器中获取beanFactory,并向beanFactory中注册一些单例bean,比如applicationArguments,printedBanner// 6)TODO 加载bean到application context,注意这里只是加载了部分bean比如mainApplication这个bean,大部分bean应该是在AbstractApplicationContext.refresh方法中被加载?这里留个疑问先// 7)》》》》》发射【ApplicationPreparedEvent】事件,标志Context容器已经准备完成 prepareContext(context, environment, listeners, applicationArguments,printedBanner);// 【7】刷新容器,这一步至关重要,以后会在分析Spring源码时详细分析,主要做了以下工作:// 1)在context刷新前做一些准备工作,比如初始化一些属性设置,属性合法性校验和保存容器中的一些早期事件等;// 2)让子类刷新其内部bean factory,注意SpringBoot和Spring启动的情况执行逻辑不一样// 3)对bean factory进行配置,比如配置bean factory的类加载器,后置处理器等// 4)完成bean factory的准备工作后,此时执行一些后置处理逻辑,子类通过重写这个方法来在BeanFactory创建并预准备完成以后做进一步的设置// 在这一步,所有的bean definitions将会被加载,但此时bean还不会被实例化// 5)执行BeanFactoryPostProcessor的方法即调用bean factory的后置处理器:// BeanDefinitionRegistryPostProcessor(触发时机:bean定义注册之前)和BeanFactoryPostProcessor(触发时机:bean定义注册之后bean实例化之前)// 6)注册bean的后置处理器BeanPostProcessor,注意不同接口类型的BeanPostProcessor;在Bean创建前后的执行时机是不一样的// 7)初始化国际化MessageSource相关的组件,比如消息绑定,消息解析等// 8)初始化事件广播器,如果bean factory没有包含事件广播器,那么new一个SimpleApplicationEventMulticaster广播器对象并注册到bean factory中// 9)AbstractApplicationContext定义了一个模板方法onRefresh,留给子类覆写,比如ServletWebServerApplicationContext覆写了该方法来创建内嵌的tomcat容器// 10)注册实现了ApplicationListener接口的监听器,之前已经有了事件广播器,此时就可以派发一些early application events// 11)完成容器bean factory的初始化,并初始化所有剩余的单例bean。这一步非常重要,一些bean postprocessor会在这里调用。// 12)完成容器的刷新工作,并且调用生命周期处理器的onRefresh()方法,并且发布ContextRefreshedEvent事件refreshContext(context);// 【8】执行刷新容器后的后置处理逻辑,注意这里为空方法afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}listeners.started(context);// 【9】调用ApplicationRunner和CommandLineRunner的run方法,实现spring容器启动后需要做的一些东西比如加载一些业务数据等callRunners(context, applicationArguments);}//部分代码省略return context;
}

上面的每行代码,每行解释,都要细细品一下,接下来我们把上面几个重要的点再深入下。

1、StopWatch stopWatch = new StopWatch();

StopWatch 是一个spring util 包下的工具类,是一个计时器,能计算代码段耗时时间。

run方法中在最开始创建StopWatch并start开始计算。在所有的操作完成后调用stop方法结束计时,然后通过log info输出格式化后的时间。

public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();stopWatch.start();// do somethingstopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); //输出计时时间}}

run方法中在最开始创建StopWatch并start开始计算。在所有的操作完成后调用stop方法结束计时,然后通过log info输出格式化后的时间。

2、configureHeadlessProperty()

//配置java.awt.headless属性configureHeadlessProperty();

这里将java.awt.headless设置为true,表示运行在服务器端,在没有显示器器和鼠标键盘的模式下照样可以工作,模拟输入输出设备功能。

做了这样的操作后,SpringBoot想干什么呢?其实是想设置该应用程序,即使没有检测到显示器,也允许其启动.对于服务器来说,是不需要显示器的,所以要这样设置.

private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
方法主体如下:
private void configureHeadlessProperty() {System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

通过方法可以看到,setProperty()方法里面又有个getProperty();这不多此一举吗?其实getProperty()方法里面有2个参数, 第一个key值,第二个是默认值,意思是通过key值查找属性值,如果属性值为空,则返回默认值 true;保证了一定有值的情况;

@SpringBootApplication
public class App {public static void main(String[] args) {ConfigurableApplicationContext run = SpringApplication.run(App.class, args);Properties properties = System.getProperties();properties.list(System.out);}
}

获取并启用监听器

这一步 通过监听器来实现初始化的的基本操作,这一步做了2件事情

  • 创建所有 Spring 运行监听器并发布应用启动事件
  • 启用监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger,getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),this.applicationStartup);
}

先看看new SpringApplicationRunListeners中的getSpringFactoriesInstances这个方法做了什么事情

getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)

这个主要是看传入了什么接口,然后会从工map中获取对应有哪些实现,然后将这些实现一一启动
我从META-INF/spring.factories看到

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

很明显,Listener主要就是初始化这个监听器

在这里插入图片描述

9、设置应用程序参数

将执行run方法时传入的参数封装成一个对象

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

创建ApplicationArguments对象,封装了args参数

@SpringBootApplication
public class App {public static void main(String[] args) {args = new String[]{"aaa","bbb","ccc"};ConfigurableApplicationContext run = SpringApplication.run(App.class, args);}
}

10、准备环境变量

准备环境变量,包含系统属性和用户配置的属性,执行的代码块在 prepareEnvironment 方法内

ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);

在这里插入图片描述
打了断点之后可以看到,它将maven和系统的环境变量都加载进来了
在这里插入图片描述

12、打印 banner 信息

显而易见,这个流程就是用来打印控制台那个很大的spring的banner的,就是下面这个东东

在这里插入图片描述
那他在哪里打印的呢?他在 SpringBootBanner.java 里面打印的,这个类实现了Banner 接口,

而且banner信息是直接在代码里面写死的;
在这里插入图片描述
有些公司喜欢自定义banner信息,如果想要改成自己喜欢的图标该怎么办呢,其实很简单,只需要在resources目录下添加一个 banner.txt 的文件即可,txt文件内容如下

                 _           _
(_)         | |
_   _  _____  ___ _ __   __| | ___  _ __   __ _
| | | |/ _ \ \/ / | '_ \ / _` |/ _ \| '_ \ / _` |
| |_| |  __/>  <| | | | | (_| | (_) | | | | (_| |
\__, |\___/_/\_\_|_| |_|\__,_|\___/|_| |_|\__, |
__/ |                                     __/ |
|___/                                     |___/
:: yexindong::

一定要添加到resources目录下,别加错了
在这里插入图片描述
只需要加一个文件即可,其他什么都不用做,然后直接启动springboot,就可以看到效果了
在这里插入图片描述
13、创建应用程序的上下文
实例化应用程序的上下文, 调用 createApplicationContext() 方法,这里就是用反射创建对象,没什么好说的;

context = createApplicationContext();

14、实例化异常报告器
异常报告器是用来捕捉全局异常使用的,当springboot应用程序在发生异常时,异常报告器会将其捕捉并做相应处理,在spring.factories 文件里配置了默认的异常报告器,
在这里插入图片描述
需要注意的是,这个异常报告器只会捕获启动过程抛出的异常,如果是在启动完成后,在用户请求时报错,异常报告器不会捕获请求中出现的异常,
在这里插入图片描述
了解原理了,接下来我们自己配置一个异常报告器来玩玩;

MyExceptionReporter.java 继承 SpringBootExceptionReporter 接口

package com.spring.application;
import org.springframework.boot.SpringBootExceptionReporter;
import org.springframework.context.ConfigurableApplicationContext;
public class MyExceptionReporter implements SpringBootExceptionReporter { private ConfigurableApplicationContext context;
// 必须要有一个有参的构造函数,否则启动会报错
MyExceptionReporter(ConfigurableApplicationContext context) { this.context = context;
}
@Override
public boolean reportException(Throwable failure) { System.out.println("进入异常报告器");
failure.printStackTrace();
// 返回false会打印详细springboot错误信息,返回true则只打印异常信息 
return false;
}
}

在 spring.factories 文件中注册异常报告器

# Error Reporters 异常报告器
org.springframework.boot.SpringBootExceptionReporter=\
com.spring.application.MyExceptionReporter

在这里插入图片描述
接着我们在application.yml 中 把端口号设置为一个很大的值,这样肯定会报错,

server:
port: 80828888

启动后,控制台打印如下图

在这里插入图片描述
15、准备上下文环境
这里准备的上下文环境是为了下一步刷新做准备的,里面还做了一些额外的事情;
在这里插入图片描述
15.1、实例化单例的beanName生成器
在 postProcessApplicationContext(context); 方法里面。使用单例模式创建 了BeanNameGenerator 对象,其实就是beanName生成器,用来生成bean对象的名称

15.2、执行初始化方法
初始化方法有哪些呢?还记得第3步里面加载的初始化器嘛?其实是执行第3步加载出来的所有初始化器,实现了ApplicationContextInitializer 接口的类

15.3、将启动参数注册到容器中
这里将启动参数以单例的模式注册到容器中,是为了以后方便拿来使用,参数的beanName 为 :springApplicationArguments

16、刷新上下文
刷新上下文已经是spring的范畴了,自动装配和启动 tomcat就是在这个方法里面完成的,还有其他的spring自带的机制在这里就不一一细说了,
在这里插入图片描述
17、刷新上下文后置处理
afterRefresh 方法是启动后的一些处理,留给用户扩展使用,目前这个方法里面是空的,

/** * Called after the context has been refreshed. * @param context the application context * @param args the application arguments */
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) { }

18、结束计时器
到这一步,springboot其实就已经完成了,计时器会打印启动springboot的时长
在这里插入图片描述
在控制台看到启动还是挺快的,不到2秒就启动完成了;

20、执行自定义的run方法

这是一个扩展功能,callRunners(context, applicationArguments) 可以在启动完成后执行自定义的run方法;有2中方式可以实现:

实现 ApplicationRunner 接口
实现 CommandLineRunner 接口
接下来我们验证一把,为了方便代码可读性,我把这2种方式都放在同一个类里面

package com.spring.init;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/** * 自定义run方法的2种方式 */
@Component
public class MyRunner implements ApplicationRunner, CommandLineRunner { @Override
public void run(ApplicationArguments args) throws Exception { System.out.println(" 我是自定义的run方法1,实现 ApplicationRunner 接口既可运行"        );
}
@Override
public void run(String... args) throws Exception { System.out.println(" 我是自定义的run方法2,实现 CommandLineRunner 接口既可运行"        );
}
}

启动springboot后就可以看到控制台打印的信息了
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/135065.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

线性代数基础-矩阵

八、矩阵的基础概念 1.矩阵 我们忘掉之前行列式的一切&#xff0c;列一种全新的数表&#xff0c;虽然长得很像&#xff0c;但是大不相同&#xff0c;首先一个区别就是矩阵不能展开成一个值&#xff0c;这里不讨论矩阵的空间意义 { a 11 x 1 a 12 x 2 a 13 x 3 . . . a 1…

求解灰度直方图,如何绘制灰度直方图(数字图像处理大题复习 P1)

文章目录 1. 画 X 轴2. 画直方图3. Complete 视频原链接 数字图像处理期末考试大题 B站链接 1. 画 X 轴 2. 画直方图 有几个 0 就在图上画多高&#xff0c;同理有几个 1 &#xff0c;X1 的地方就画多高 3. Complete 这里的情况比较平均&#xff0c;一般来说不会这么平均&a…

CSS选择器笔记

A plate #id #fancy A B plate apple #id A #fancy pickle .classname .small A.className orange.small #id.className #big.wide A,B both plate,bento * all A * plate * AB 紧跟在盘子后的苹果 plate apple A~B 跟在盘子后面所有的泡菜 plate~b…

堆内存与栈内存

文章目录 1. 栈内存2. 堆内存3. 区别和联系参考资料 1. 栈内存 栈内存是为线程留出的临时空间 每个线程都有一个固定大小的栈空间&#xff0c;而且栈空间存储的数据只能由当前线程访问&#xff0c;所以它是线程安全的。栈空间的分配和回收是由系统来做的&#xff0c;我们不需…

LeetCode 周赛上分之旅 #45 精妙的 O(lgn) 扫描算法与树上 DP 问题

⭐️ 本文已收录到 AndroidFamily&#xff0c;技术和职场问题&#xff0c;请关注公众号 [彭旭锐] 和 BaguTree Pro 知识星球提问。 学习数据结构与算法的关键在于掌握问题背后的算法思维框架&#xff0c;你的思考越抽象&#xff0c;它能覆盖的问题域就越广&#xff0c;理解难度…

Qt基于paintEvent自定义CharView

Qt基于paintEvent自定义CharView 鼠标拖动&#xff0c;缩放&#xff0c;区域缩放&#xff0c; 针对x轴&#xff0c;直接上代码 charview.h #ifndef CHARVIEW_H #define CHARVIEW_H#include <QWidget> #include <QPainter> #include <QPaintEvent> #inclu…

unity学习第1天

本身也具有一些unity知识&#xff0c;包括Eidtor界面使用、Shader效果实现、性能分析&#xff0c;但对C#、游戏逻辑不太清楚&#xff0c;这次想从开发者角度理解游戏&#xff0c;提高C#编程&#xff0c;从简单的unity游戏理解游戏逻辑&#xff0c;更好的为工作服务。 unity201…

CMU 15-445 Project #3 - Query Execution(Task #1、Task #2)

文章目录 一、题目链接二、准备工作三、SQL 语句执行流程四、BusTub 表结构五、Task #1 - Access Method Executors5.1 顺序扫描执行器5.2 插入执行器5.3 删除执行器5.4 索引扫描执行器 六、Task #2 - Aggregation & Join Executors6.1 聚合执行器6.2 循环连接执行器6.3 索…

python 二手车数据分析以及价格预测

二手车交易信息爬取、数据分析以及交易价格预测 引言一、数据爬取1.1 解析数据1.2 编写代码爬1.2.1 获取详细信息1.2.2 数据处理 二、数据分析2.1 统计分析2.2 可视化分析 三、价格预测3.1 价格趋势分析(特征分析)3.2 价格预测 引言 本文着眼于车辆信息&#xff0c;结合当下较…

Linux的调试工具 - gdb(超详细)

Linux的调试工具 - gdb 1. 背景2. 开始使用指令的使用都用下面这个C语言简单小代码来进行演示&#xff1a;1. list或l 行号&#xff1a;显示文件源代码&#xff0c;接着上次的位置往下列&#xff0c;每次列10行。2. list或l 函数名:列出某个函数的源代码。3. r或run: 运行程序。…

7.前端·新建子模块与开发(自动生成)

文章目录 学习地址视频笔记自动代码生成模式开发增删改查功能调试功能权限分配 脚本实现权限分配 学习地址 https://www.bilibili.com/video/BV13g411Y7GS/?p15&spm_id_frompageDriver&vd_sourceed09a620bf87401694f763818a31c91e 视频笔记 自动代码生成模式开发 …

简单返回封装实体类(RespBean)

RespBean的作用 返回状态码&#xff0c;返回信息&#xff0c;返回数据 package com.example.entity;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;Data AllArgsConstructor NoArgsConstructor public class RespBean {private lon…

AndroidStudio 安装与配置【安装教程】

1.下载软件 进入官网https://developer.android.google.cn/studio&#xff0c;直接点击下载 2.阅读并同意协议书 直接下滑至最底部 如果这里出现了无法访问 官方地址&#xff1a;https://redirector.gvt1.com/edgedl/android/studio/install/2022.3.1.19/android-studio-2022.…

Windows11系统C盘用户文件夹下用户文件夹为中文,解决方案

说明&#xff1a; 1. 博主电脑为Windows11操作系统&#xff0c;亲测有效&#xff0c;修改后无任何影响&#xff0c;软件都可以正常运行&#xff01; 2. Windows10系统还不知道可不可行&#xff0c;因为Windows11的计算机管理中没有本地用户和组&#xff0c;博主在csdn上看到很…

后端中间件安装与启动(Redis、Nginx、Nacos、Kafka)

后端中间件安装与启动 RedisNginxNacosKafka Redis 1.打开cmd终端&#xff0c;进入redis文件目录 2.输入redis-server.exe redis.windows.conf即可启动&#xff0c;不能关闭cmd窗口 &#xff08;端口配置方式&#xff1a;redis目录下的redis.windows.conf配置文件&#xff0c;…

Pytorch-MLP-Mnist

文章目录 model.pymain.py参数设置注意事项初始化权重如果发现loss和acc不变关于数据下载关于输出格式 运行图 model.py import torch.nn as nn import torch.nn.functional as F import torch.nn.init as initclass MLP_cls(nn.Module):def __init__(self,in_dim28*28):super…

快递、外卖、网购自动定位及模糊检索收/发件地址功能实现

概述 目前快递、外卖、团购、网购等行业 &#xff1a;为了简化用户在收发件地址填写时的体验感&#xff0c;使用辅助定位及模糊地址检索来丰富用户的体验 本次demo分享给大家&#xff1b;让大家理解辅助定位及模糊地址检索的功能实现过程&#xff0c;以及开发出自己理想的作品…

【C++初阶】C++STL详解(四)—— vector的模拟实现

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C初阶 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 【C初阶】CSTL详解&#xff08;三…

Python 文件写入操作

视频版教程 Python3零基础7天入门实战视频教程 w模式是写入&#xff0c;通过write方法写入内容。 # 打开文件 模式w写入&#xff0c;文件不存在&#xff0c;则自动创建 f open("D:/测试3.txt", "w", encoding"UTF-8")# write写入操作 内容写入…

C++---继承

继承 前言继承的概念及定义继承的概念继承定义继承关系和访问限定符 基类和派生类对象赋值转换继承中的作用域派生类的默认成员函数继承与友元继承与静态成员**多重继承**多继承下的类作用域菱形继承虚继承使用虚基类 支持向基类的常规类型转换 前言 在需要写Father类和Mother…