大家好,又见面了,我是你们的朋友全栈君。
前言:这是在慕课网上学习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…)
基本使用:
通过静态方法进行执行,第二个参数是启动参数,java进程的启动参数可以通过外界进行传输.
可以通过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的生命周期,作用域,包括源码的一些分析,并将其进行简单的穿插,其中包括外部化配置,事件这样的机制。
这里将主要分准备阶段与运行阶段来进行讲解。
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应用监听方式。
Spring Framework的衍生技术到了SpringBoot中来
SpringApplication:这是一个类,一般都是由它运行run方法来进行应用的启动。
SpringApplication Builder API:用build方式可以很方便的构建出相应的控制行为。
SpringApplication的运行监听器:与前面的事件监听有所区别,运行监听器允许我们扩展,默认情况下SpringBoot提供了一种运行监听器的实现。
SpringApplication参数:参数可以应用到程序中做一些配置。
SpringApplication故障分析:由于自动化装配或者starter搞完之后可能出现一些情况,比如端口被占,资源被锁定,可以提供一些报告,帮助人员进行排查分析。
Spring Boot应用事件/监听器:都是构建在Spring基础上的。
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();
}
在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 {
}
}
根据当前应用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;
}
引导类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;
}
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
我们可以模仿其配置,自定义一个实现自己的上下文初始化器。
在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());
}
}
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);
}
}
监听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运行监听器
监听: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;
}
加载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();
}
}
...
}
监听器加载后在对应的阶段方法会执行对应的方法。
同时因为EventPublishingRunListener实现了SpringApplicationRunListener,而EventPublishingRunListener会发出与阶段对应的事件,所以在对应的阶段也会有对应的事件出现。
比如在Starting阶段。
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
//...
public void starting() {
this.initialMulticaster.multicastEvent(
new ApplicationStartingEvent(this.application, this.args));
}
//...
}
在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()…。
声明:这个是看了源码后的个人理解,不保证一定正确,只提供一定的参考.
在准备阶段加载实现了ApplicationListener的监听器,
在运行阶段会加载所有实现了SpringApplicationRunListener的类,然后根据阶段调用所有SpringApplicationRunListener类的方法,比如在Spring应用刚启动时调用所有实现类的starting的方法,可以看到下面就是主动调用了starting方法,在Spring应用正在运行时调用所有实现类的running方法.
具体阶段会调用的方法可以见下图
其中有一个实现了SpringApplicationRunListener的类是EventPublishingRunListener,在这个类的构造函数中,将所有实现了ApplicationListener类的监听器添加到了SimpleApplicationEventMulticaster中.
当我们把所有监听器都添加到SimpleApplicationEventMulticaster后,我们就可以用它来发布事件,当有事件发布时,会检测事件类型与SimpleApplicationEventMulticaster中的实现了ApplicationListener的监听器的泛型是否符合,若符合则调用.
总之就是所有监听器在一个类里,当有事件发布时,此类会将所有监听器检查一遍,符合就调用.
同时EventPublishingRunListener也在对应的阶段发布了对应的事件,所以我们在对应的阶段会有对应的事件.
根据准备阶段推断的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();
}
}
总的来说:
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/195466.html原文链接:https://javaforall.cn