springboot系列4,SpringApplication准备阶段
关于SpringBoot,大致可分为基础技术和衍生技术。
基础技术主要是关于SpringFramework的,那SpringFramework中,我们可大致分为几块:
Spring 模式注解:例如@Service、@Component等,详情见之前文章。
Spring 应用上下文:无需多解释,核心组件,用来装配bean、相应的生命周期。
Spring 工厂加载机制:之前文章自动装配。
Spring 应用上下文初始化器:在spring上下文没有初始化之前做一些调整和变化等。
Spring Environment抽象接口:一个环境,统一所有环境,包括配置属性、profile等。
Spring 应用事件/监听器:扩展了java应用监听的方式。
衍生技术或者是SpringFramework的衍生技术,主要是SpringBoot的特性:
SpringApplication
SpringApplication Builder API:之前文章有用过,可以进行链式编写,比较方便。
SpringApplication运行监听器
SpringApplication参数
SpringApplication故障分析
SpringBoot应用事件/监听器
SpringApplication官方定义:SpringApplication类通过其中的main方法来引导Spring应用启动。很多情况下都可以使用SpringApplication.run的静态方法的方式启动。
所以SpringApplication是Spring应用的引导类,可以提供便利的自定义行为方法。
使用场景:嵌入式Web应用和非Web应用
拓展:web应用分为嵌入式场景和非嵌入式场景,springboot可以部署在Tomcat7、jetty7、servlet容器中,部署在servlet容器中时,SpringApplication就不能用了。
自定义SpringApplication
通过SpringApplication的API调整
SpringApplication springApplication = new SpringApplication(Application.class);
springApplication.setBannerMode(Banner.Mode.CONSOLE);
springApplication.setWebApplicationType(WebApplicationType.NONE);
springApplication.setAdditionalProfiles("prod");
springApplication.setHeadless(true);
通过SpringApplicationBuilder的API调整
new SpringApplicationBuilder(Application.class)
.bannerMode(Banner.Mode.CONSOLE)
.web(WebApplicationType.NONE)
.profiles("prod")
.headless(true)
.run(args);
SpringApplication准备阶段
配置:Spring Bean来源
在Spring启动时,很多功能组件是以bean的方式承载的,而bean的配置需要来源,java配置class或xml上下文配置文件集合,用于springboot的BeanDefinitionLoader读取,并将配置源解析加载为Spring Bean定义。
java配置方式:SpringApplication就是一个配置源,@SpringApplication中包含@Component,所以这个类就是个源,在上面代码new SpringApplication时,把自身这个类配置进去。
对于SpringApplicationBuilder,看源码可以看出,来源可以不止一个。
通常情况下,我们会用main方法启动,如下
/**
* {@link SpringApplication} 引导类
*/
@SpringBootApplication
public class SpringApplicationBootstrap {
public static void main(String[] args) {
SpringApplication.run(SpringApplicationBootstrap.class, args);
}
}
但其实,不是非得是main方法,例如
/**
* {@link SpringApplication} 引导类
*/
public class SpringApplicationBootstrap {
public static void main(String[] args) {
SpringApplication.run(springMethod.class, args);
}
@SpringBootApplication
public static class springMethod{
}
}
一样可以运行成功
在配置源的时候,需要使用setSources的方式,源码:
源码注释中,source可以是类名、包名或xml配置文件路径。
所以也可以变为:
/**
* {@link SpringApplication} 引导类
*/
public class SpringApplicationBootstrap {
public static void main(String[] args) {
Set<String> sources = new HashSet();
sources.add(springMethod.class.getName());
SpringApplication springApplication = new SpringApplication();
springApplication.setSources(sources);
ConfigurableApplicationContext context = springApplication.run(args);
System.out.println(context.getBean(springMethod.class));
}
@SpringBootApplication
public static class springMethod{
}
}
运行结果:
推断:分为Web应用类型和主引导类(Main Class)
推断web类型:
因为不同类型在运行时不同,例如普通类型运行后直接结束,而web类型会持续运行,有端口号等,所以需要判断类型。在有tomcat依赖的时候,类型会自动切换成web类型。根据当前应用 ClassPath 中是否存在相关实现类来推断 Web 应用的类型。包括:
Web Reactive: WebApplicationType.REACTIVE
Web Servlet: WebApplicationType.SERVLET
非 Web: WebApplicationType.NONE
SpringApplication类中的deduceWebApplicationType即推断过程,源码如下(每个版本都有调整,此处用2.2.2版本):
static WebApplicationType deduceFromApplicationContext(Class<?> applicationContextClass) {
if (isAssignable("org.springframework.web.context.WebApplicationContext", applicationContextClass)) {
return SERVLET;
} else {
return isAssignable("org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext"
, applicationContextClass) ? REACTIVE : NONE;
}
}
spring会扫描目录,如果包含相关关键字,会根据不同的type,选择不同的应用类型。由此可看出,有mvc就不会用reacitive。
想把web类型变为普通类型,设置
springApplication.setWebApplicationType(WebApplicationType.NONE);
推断引导类:
在上面代码中有一个spring来源是单独写的类,main方法中SpringApplication加载的并不是main方法所在的类,那么这时我们并不知道main所在的类是哪个,这个时候需要找,上源码:
通过源码可知,系统会找到栈中的错误信息,通过遍历关键词“main”,找到main方法所在的类名并返回。
通过debug查看栈情况:
所以由此可知,不管是否传的是main方法,spring都会调用栈信息搜索main所在的类。
加载:应用上下文初始化器和应用事件监听器
加载应用上下文初始化器:
利用spring工厂加载机制,实例化ApplicationContextInitializer实现类,并排序对象集合。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 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;
}
实现类: org.springframework.core.io.support.SpringFactoriesLoader
SpringFactoriesLoader通过加载META-INF/spring.factories文件,加载初始化器,当然多个包可能会有多个文件,相对应的也会有多个初始化器,那么需要对其进行排序,即使用AnnotationAwareOrderComparator.sort(instances);进行,看源码可看出,有两个关键词,order注解、ordered接口,都是用来定义顺序的
当然,如果排序是非必须的,如果不排序,spring会按照默认规则进行加载。
自定义应用上下文初始化器
@Order(Ordered.HIGHEST_PRECEDENCE)
public class HelloWorldApplicationContextInitializer<C extends ConfigurableApplicationContext> implements ApplicationContextInitializer<C> {
@Override
public void initialize(C configurableApplicationContext) {
System.out.println("ConfigurableApplicationContext.id = " + configurableApplicationContext.getId());
}
}
public class AfterHelloWorldApplicationContextInitializer<C extends ConfigurableApplicationContext> implements ApplicationContextInitializer<C> , Ordered {
@Override
public void initialize(C configurableApplicationContext) {
System.out.println("after ConfigurableApplicationContext.id = " + configurableApplicationContext.getId());
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
META-INF/spring.factories中定义:
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
com.haozirou.springboot.context.AfterHelloWorldApplicationContextInitializer,\
com.haozirou.springboot.context.HelloWorldApplicationContextInitializer
运行结果:
加载应用事件监听器
ApplicationListener,原理同初始化器一样。
以autoconfigure为例,他的factories中配置的监听器为BackgroundPreinitializer
打开发现
其中实现ApplicationListener,监听SpringApplicationEvent事件,该事件是springboot的事件,而非spring事件,其继承的ApplicationEvent事件才是spring事件,再往上追溯可找到EventObject接口,他是所有事件的源,
自定义事件监听器
/**
* HelloWorld {@link ApplicationListener} 监听 {@link ContextRefreshedEvent} 事件
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
public class HelloWorldApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
System.out.println("HelloWorld : " + contextRefreshedEvent.getApplicationContext().getId());
System.out.println("and timestamp:" + contextRefreshedEvent.getTimestamp());
}
}
/**
* after HelloWorld {@link ApplicationListener} 监听 {@link ContextRefreshedEvent} 事件
* 监听同一个事件,因为不同事件启动的时机可能不太一样,不好做比较
*/
@Order(Ordered.LOWEST_PRECEDENCE)
public class AfterHelloWorldApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
System.out.println("after HelloWorld : " + contextRefreshedEvent.getApplicationContext().getId());
System.out.println("after and timestamp:" + contextRefreshedEvent.getTimestamp());
}
}
# Application Listeners
org.springframework.context.ApplicationListener=\
com.haozirou.springboot.listener.AfterHelloWorldApplicationListener,\
com.haozirou.springboot.listener.HelloWorldApplicationListener
执行结果:
至此,SpringApplication准备阶段完成,会看SpringApplication源码,
先加载配置源,接下来推断web应用类型,在初始化上下文初始化器,接下来初始化事件监听器,最后推断引导类。
上一篇: java使用aes加密文件内容
下一篇: Springboot整合Druid