大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺
前言:这是在慕课网上学习Spring Boot2.0深度实践之核心技术篇 时所做的笔记,主要供本人复习之用。
目录
2.4 加载应用上下文初始器(ApplicationContextInitializer)
第一章 概要
什么是SpringApplication?
官方文档中只是写SpringApplication是一个类,提供一些便利的功能,引导Spring的程序进行启动,在一个main的方法里面。在后续的章节里它写了SpringApplication的很多特性,包括错误分析报告等等。
@SpringBootApplication
public class RepApplication {
public static void main(String[] args) {
//要理解的SpringApplication
SpringApplication.run(RepApplication.class, args);
}
}
SpringApplication定义:Spring应用引导类,提供便利的自定义行为方法。
SpringApplication应用场景:嵌入式Web应用和非Web应用,嵌入式场景指的是tomcat,jetty中。
SpringApplication运行:运行:SpringApplication.run(String…)
基本使用:
1.1 基本使用
1.1.1 直接运行
通过静态方法进行执行,第二个参数是启动参数,java进程的启动参数可以通过外界进行传输.
1.1.2 自定义
可以通过SpringApplication API调整:
SpringApplication springApplication = new SpringApplication(DiveInSpringBootApplication.class);
springApplication.setBannerMode(Banner.Mode.CONSOLE);
springApplication.setWebApplicationType(WebApplicationType.NONE);
springApplication.setAdditionalProfiles("prod"); springApplication.setHeadless(true);
可以通过SpringApplicationBuilder调整:
new SpringApplicationBuilder(DiveInSpringBootApplication.class)
.bannerMode(Banner.Mode.CONSOLE)
.web(WebApplicationType.NONE)
.profiles("prod")
.headless(true)
.run(args)
我们这里不只是要理解官方网页里提供的特性,还要理解SpringApplication的生命周期,作用域,包括源码的一些分析,并将其进行简单的穿插,其中包括外部化配置,事件这样的机制。
这里将主要分准备阶段与运行阶段来进行讲解。
1.2 用到的基础技术
Spring Framework:
Spring模式注解:@component @Service @Repositroy @Configuration ,模式注解允许我们使用派生的方式自定义,也可以使用SpringBoot中的注解,比如@SpringBootApplication。
Spring应用上下文:Spring应用上下文是Spring一个很核心的组件,用它来装配Bean的生命周期,在SpringBoot中有很多上下文,与一些特性绑定。
Spring工厂加载机制:配置@EnableAutoConfiguration来加载Spring.factories里的key,配置实现类放在其上。
Spring应用上下文初始化器:在SpringMVC中用到,对Spring上下文做了一些修改,也就是说在Spring上下文未启动之前,将其做一些相应的调整与变化。
Spring Environment抽象:Spring3.1中提出的抽象的接口,这个接口叫做Environment,这个接口统一了所有环境,包括里面的配置属性与profile。
Spring应用事件/监听器:扩展java Spring应用监听方式。
1.3 用到的扩展衍生技术
Spring Framework的衍生技术到了SpringBoot中来
SpringApplication:这是一个类,一般都是由它运行run方法来进行应用的启动。
SpringApplication Builder API:用build方式可以很方便的构建出相应的控制行为。
SpringApplication的运行监听器:与前面的事件监听有所区别,运行监听器允许我们扩展,默认情况下SpringBoot提供了一种运行监听器的实现。
SpringApplication参数:参数可以应用到程序中做一些配置。
SpringApplication故障分析:由于自动化装配或者starter搞完之后可能出现一些情况,比如端口被占,资源被锁定,可以提供一些报告,帮助人员进行排查分析。
Spring Boot应用事件/监听器:都是构建在Spring基础上的。
第二章 SpringApplication的准备阶段
new的初始化阶段即为准备阶段。
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
在准备阶段会
配置SpringBean的来源
推断web应用类型
加载应用上下文初始器
加载应用事件监听器
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
2.1 配置SpringBean的来源
在Spring容器或应用启动时,里面的许多功能组件是以Bean方式来承载的,来源一般有两种一种是java配置,一种是xml文件.由BeanDefinitionLoader来读取。
BeanDefinitionLoader:
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
Assert.notNull(registry, "Registry must not be null");
Assert.notEmpty(sources, "Sources must not be empty");
this.sources = sources;
//注解读取
this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
//xml读取
this.xmlReader = new XmlBeanDefinitionReader(registry);
if (isGroovyPresent()) {
this.groovyReader = new GroovyBeanDefinitionReader(registry);
}
this.scanner = new ClassPathBeanDefinitionScanner(registry);
this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}
为什么会做xml的配置源呢? 遗留系统有一些组件已经封装的非常好了,不需要再重复开发,可以把Spring应用配置文件放到源中进行配置。
下面就是传入的RepApplication.class就是一个java配置
@SpringBootApplication
//java配置
public class RepApplication {
public static void main(String[] args) {
SpringApplication.run(RepApplication.class, args);
}
}
也可以自定义java配置,可以看出,传入的class,不一定是运行类的class,也可以是自己定义的class,只要有SpringBootApplication注解即可。
public class SpringApplicationBootstrap {
public static void main(String[] args) {
Set<String> sources = new HashSet();
// 配置Class 名称
sources.add(ApplicationConfiguration.class.getName());
SpringApplication springApplication = new SpringApplication();
springApplication.setSources(sources);
springApplication.run(args);
}
@SpringBootApplication
public static class ApplicationConfiguration {
}
}
2.2 推断Web应用类型
根据当前应用ClassPath中是否存在相关的实现类来推断Web应用的类型,包括:
Web Reactive: WebApplicationType.REACTIVE
Web Servlet: WebApplicationType.SERVLET
非 Web: WebApplicationType.NONE
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
2.3 推断引导类
引导类RepApplication,通常我们的引导类是放在SpringApplication.run的方法参数中的,为什么还要去推导呢?
@SpringBootApplication
public class RepApplication {
public static void main(String[] args) {
//run的参数RepApplication.class
SpringApplication.run(RepApplication.class, args);
}
}
这个在2.1中的代码已经可以说明了,我们传入class的不一定是我们的引导类(main存在的类)。
查看源码发现,其是根据堆栈来判断的,当我们代码执行时,堆栈中会存储我们的执行方法,与执行的类,当检测到某个方法为main的时候,其所属的类就是我们的引导类。
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
2.4 加载应用上下文初始器(ApplicationContextInitializer)
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
利用Spring工厂加载机制,实例化ApplicationContextInitializer,并排序对象集合。
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryClass, "'factoryClass' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
}
List<T> result = new ArrayList<>(factoryNames.size());
for (String factoryName : factoryNames) {
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
关于类加载的顺序:
可以实现Ordered接口来定义顺序,数字越小优先级越高,
也可以使用Order注解来定义顺序。
不定义也没关系,会有内置的顺序。
在autoConfigure的jar包中的spring.factories能找到其配置项
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
2.4.1 自定义上下文初始器
我们可以模仿其配置,自定义一个实现自己的上下文初始化器。
在resource中新建META-INF,在其中新建spring.factories文件,书写一下配置。
org.springframework.context.ApplicationContextInitializer=\
com.imooc.diveinspringboot.context.HelloWorldApplicationContextInitializer
新的上下文类的书写可以参org.springframework.context.ApplicationContextInitializer接口的实现类。
下面的类也是仿照其实现类书写的。
@Order(Ordered.HIGHEST_PRECEDENCE)
public class HelloWorldApplicationContextInitializer<C extends ConfigurableApplicationContext>
implements ApplicationContextInitializer<C> {
@Override
public void initialize(C applicationContext) {
System.out.println("ConfigurableApplicationContext.id = "+ applicationContext.getId());
}
}
2.5 加载应用事件监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
事件监听器的加载与上面上下文的加载方式差不多,在autoConfigure的jar包中的spring.factories能找到其配置项
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
打开org.springframework.boot.autoconfigure.BackgroundPreinitializer,发现其监听了另外一个SpringBoot事件SpringApplicationEvent
@Order(LoggingApplicationListener.DEFAULT_ORDER + 1)
public class BackgroundPreinitializer
implements ApplicationListener<SpringApplicationEvent> {
...
}
Spring的事件与一个ApplicationEvent相关,它继承了一个标记接口EventObject,它是所有事件的源,在Application中除了继承过来的源(比如鼠标点击)之外还有一个当时发生的时间,
public abstract class ApplicationEvent extends EventObject {
/** use serialVersionUID from Spring 1.2 for interoperability. */
private static final long serialVersionUID = 7099057708183571937L;
/** System time when the event happened. */
private final long timestamp;
}
Spring中有ApplicationContextEvent,这个事件是关于Spring上下文的事件。
public abstract class ApplicationContextEvent extends ApplicationEvent {
public ApplicationContextEvent(ApplicationContext source) {
super(source);
}
public final ApplicationContext getApplicationContext() {
return (ApplicationContext) getSource();
}
}
ApplicationContextEvent有一个实现类,ContextRefreshEvent,当上下文刷新或者启动完成的时候,
public class ContextRefreshedEvent extends ApplicationContextEvent {
public ContextRefreshedEvent(ApplicationContext source) {
super(source);
}
}
2.5.1 实现自定义事件监听器
监听ContextRefreshEvent
@Order(Ordered.HIGHEST_PRECEDENCE)
public class HelloWorldApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("HelloWorld : " + event.getApplicationContext().getId()
+ " , timestamp : " + event.getTimestamp());
}
}
在resource中新建META-INF,在其中新建spring.factories文件,书写一下配置。
org.springframework.context.ApplicationListener=\
com.imooc.diveinspringboot.listener.HelloWorldApplicationListener
配置完成,启动应用,即可监听上下文刷新事件。
第三章 SpringApplication运行阶段
加载:SpringApplication运行监听器
运行:SpringApplication运行监听器
监听:SpringBoot事件、Spring事件
创建:应用上下文、Enviroment、其它(不重要),应用上下文创建后会被应用上下文初始化器初始化,Enviroment是抽象的环境对象。
失败:故障分析报告。
回调:CommandLineRunner、ApplicationRunner
整隔运行代码如下:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
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;
}
3.1 加载运行监听器
3.1.1 加载监听器
加载SpringApplication运行监听器(SpringApplicationRunListeners)
利用Spring工厂加载机制,读取SpringApplicationRunListener对象集合,并且封装到组合类SpringApplicationRunListeners。
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));
}
SpringApplicationRunListeners称为组合对象,当我们在实现某个接口的时候,可以通过foreach的方式迭代执行,是一种很常见的设计模式。
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();
}
}
...
}
3.1.2 运行监听器
监听器加载后在对应的阶段方法会执行对应的方法。
同时因为EventPublishingRunListener实现了SpringApplicationRunListener,而EventPublishingRunListener会发出与阶段对应的事件,所以在对应的阶段也会有对应的事件出现。
比如在Starting阶段。
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
//...
public void starting() {
this.initialMulticaster.multicastEvent(
new ApplicationStartingEvent(this.application, this.args));
}
//...
}
3.1.3 自定义监听器
在resource中新建META-INF,在其中新建spring.factories文件,书写一下配置。
org.springframework.boot.SpringApplicationRunListener=\
com.imooc.diveinspringboot.run.HelloWorldRunListener
然后实现SpringApplicationRunListener接口即可自定义监听器,但是要注意这里必须要有如下的构造方法。
public class HelloWorldRunListener implements SpringApplicationRunListener {
public HelloWorldRunListener(SpringApplication application, String[] args) {
}
@Override
public void starting() {
System.out.println("HelloWorldRunListener.starting()...");
}
//省略其余实现
}
我们会发现在打印banner的时候,会先打印HelloWorldRunListener.starting()…。
3.1.4 SpringBoot的监听机制
声明:这个是看了源码后的个人理解,不保证一定正确,只提供一定的参考.
在准备阶段加载实现了ApplicationListener的监听器,
在运行阶段会加载所有实现了SpringApplicationRunListener的类,然后根据阶段调用所有SpringApplicationRunListener类的方法,比如在Spring应用刚启动时调用所有实现类的starting的方法,可以看到下面就是主动调用了starting方法,在Spring应用正在运行时调用所有实现类的running方法.
具体阶段会调用的方法可以见下图
其中有一个实现了SpringApplicationRunListener的类是EventPublishingRunListener,在这个类的构造函数中,将所有实现了ApplicationListener类的监听器添加到了SimpleApplicationEventMulticaster中.
当我们把所有监听器都添加到SimpleApplicationEventMulticaster后,我们就可以用它来发布事件,当有事件发布时,会检测事件类型与SimpleApplicationEventMulticaster中的实现了ApplicationListener的监听器的泛型是否符合,若符合则调用.
总之就是所有监听器在一个类里,当有事件发布时,此类会将所有监听器检查一遍,符合就调用.
同时EventPublishingRunListener也在对应的阶段发布了对应的事件,所以我们在对应的阶段会有对应的事件.
3.2 创建Spring应用上下文
根据准备阶段推断的Web应用类型创建对应的ConfigurableApplicationContext实例.根据类型有三种不同而实例.
由SpringApplication的run方法->createApplicationContext方法
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应用类型创建对应的ConfigurableEnviroment实例.根据类型有三种不同而实例.
由SpringApplication的run方法->prepareEnvironment方法->getOrCreateEnvironment方法
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();
}
}
总的来说:
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/195466.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...