大家好,又见面了,我是你们的朋友全栈君。
前言:这是在慕课网上学习Spring Boot2.0深度实践之核心技术篇 时所做的笔记,主要供本人复习之用。
目录
第一章 概要
1.1 基本使用
1.1.1 直接运行
1.1.2 自定义
1.2 用到的基础技术
1.3 用到的扩展衍生技术
第二章 SpringApplication的准备阶段
2.1 配置SpringBean的来源
2.2 推断Web应用类型
2.3 推断引导类
2.4 加载应用上下文初始器(ApplicationContextInitializer)
2.4.1 自定义上下文初始器
2.5 加载应用事件监听器
2.5.1 实现自定义事件监听器
第三章 SpringApplication运行阶段
3.1 加载运行监听器
3.1.1 加载监听器
3.1.2 运行监听器
3.1.3 自定义监听器
3.1.4 SpringBoot的监听机制
3.2 创建Spring应用上下文
第一章 概要
什么是SpringApplication?
官方文档中只是写SpringApplication是一个类,提供一些便利的功能,引导Spring的程序进行启动,在一个main的方法里面。在后续的章节里它写了SpringApplication的很多特性,包括错误分析报告等等。
代码语言:javascript复制@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调整:
代码语言:javascript复制SpringApplication springApplication = new SpringApplication(DiveInSpringBootApplication.class);
springApplication.setBannerMode(Banner.Mode.CONSOLE);
springApplication.setWebApplicationType(WebApplicationType.NONE);
springApplication.setAdditionalProfiles("prod"); springApplication.setHeadless(true);
可以通过SpringApplicationBuilder调整:
代码语言:javascript复制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的初始化阶段即为准备阶段。
代码语言:javascript复制public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
在准备阶段会
配置SpringBean的来源
推断web应用类型
加载应用上下文初始器
加载应用事件监听器
代码语言:javascript复制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:
代码语言:javascript复制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配置
代码语言:javascript复制@SpringBootApplication
//java配置
public class RepApplication {
public static void main(String[] args) {
SpringApplication.run(RepApplication.class, args);
}
}
也可以自定义java配置,可以看出,传入的class,不一定是运行类的class,也可以是自己定义的class,只要有SpringBootApplication注解即可。
代码语言:javascript复制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
代码语言:javascript复制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的方法参数中的,为什么还要去推导呢?
代码语言:javascript复制@SpringBootApplication
public class RepApplication {
public static void main(String[] args) {
//run的参数RepApplication.class
SpringApplication.run(RepApplication.class, args);
}
}
这个在2.1中的代码已经可以说明了,我们传入class的不一定是我们的引导类(main存在的类)。
查看源码发现,其是根据堆栈来判断的,当我们代码执行时,堆栈中会存储我们的执行方法,与执行的类,当检测到某个方法为main的时候,其所属的类就是我们的引导类。
代码语言:javascript复制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)
代码语言:javascript复制setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
利用Spring工厂加载机制,实例化ApplicationContextInitializer,并排序对象集合。
代码语言:javascript复制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能找到其配置项
代码语言:javascript复制# Initializers
org.springframework.context.ApplicationContextInitializer=
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
2.4.1 自定义上下文初始器
我们可以模仿其配置,自定义一个实现自己的上下文初始化器。
在resource中新建META-INF,在其中新建spring.factories文件,书写一下配置。
代码语言:javascript复制org.springframework.context.ApplicationContextInitializer=
com.imooc.diveinspringboot.context.HelloWorldApplicationContextInitializer
新的上下文类的书写可以参org.springframework.context.ApplicationContextInitializer接口的实现类。
下面的类也是仿照其实现类书写的。
代码语言:javascript复制@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 加载应用事件监听器
代码语言:javascript复制setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
事件监听器的加载与上面上下文的加载方式差不多,在autoConfigure的jar包中的spring.factories能找到其配置项
代码语言:javascript复制# Application Listeners
org.springframework.context.ApplicationListener=
org.springframework.boot.autoconfigure.BackgroundPreinitializer
打开org.springframework.boot.autoconfigure.BackgroundPreinitializer,发现其监听了另外一个SpringBoot事件SpringApplicationEvent
代码语言:javascript复制@Order(LoggingApplicationListener.DEFAULT_ORDER 1)
public class BackgroundPreinitializer
implements ApplicationListener<SpringApplicationEvent> {
...
}
Spring的事件与一个ApplicationEvent相关,它继承了一个标记接口EventObject,它是所有事件的源,在Application中除了继承过来的源(比如鼠标点击)之外还有一个当时发生的时间,
代码语言:javascript复制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上下文的事件。
代码语言:javascript复制public abstract class ApplicationContextEvent extends ApplicationEvent {
public ApplicationContextEvent(ApplicationContext source) {
super(source);
}
public final ApplicationContext getApplicationContext() {
return (ApplicationContext) getSource();
}
}
ApplicationContextEvent有一个实现类,ContextRefreshEvent,当上下文刷新或者启动完成的时候,
代码语言:javascript复制public class ContextRefreshedEvent extends ApplicationContextEvent {
public ContextRefreshedEvent(ApplicationContext source) {
super(source);
}
}
2.5.1 实现自定义事件监听器
监听ContextRefreshEvent
代码语言:javascript复制@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文件,书写一下配置。
代码语言:javascript复制org.springframework.context.ApplicationListener=
com.imooc.diveinspringboot.listener.HelloWorldApplicationListener
配置完成,启动应用,即可监听上下文刷新事件。
第三章 SpringApplication运行阶段
加载:SpringApplication运行监听器
运行:SpringApplication运行监听器
监听:SpringBoot事件、Spring事件
创建:应用上下文、Enviroment、其它(不重要),应用上下文创建后会被应用上下文初始化器初始化,Enviroment是抽象的环境对象。
失败:故障分析报告。
回调:CommandLineRunner、ApplicationRunner
整隔运行代码如下:
代码语言:javascript复制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。
代码语言:javascript复制SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
代码语言:javascript复制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的方式迭代执行,是一种很常见的设计模式。
代码语言:javascript复制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阶段。
代码语言:javascript复制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文件,书写一下配置。
代码语言:javascript复制org.springframework.boot.SpringApplicationRunListener=
com.imooc.diveinspringboot.run.HelloWorldRunListener
然后实现SpringApplicationRunListener接口即可自定义监听器,但是要注意这里必须要有如下的构造方法。
代码语言:javascript复制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方法
代码语言:javascript复制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方法
代码语言:javascript复制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