Spring基础系列-容器启动流程(2)
原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9503210.html
一、概述
这里是Springboot项目启动大概流程,区别于SSM框架项目启动流程。
二、启动流程
Springboot项目可以直接从main方法启动。
源码1-来自DemoApplicaiton(应用自定义的)
@SpringBootApplication
public class DemoApplication { public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
首先调用的是SpringApplication中的run方法。
源码2-来自:SpringApplication
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
} public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
第一个run方法会调用第二个run方法。
在第二个run方法中有两步:
第一步:创建SpringApplicaiton实例
第二步:调用run方法
首先我们先创建SpringApplication实例
源码3-来自:SpringApplication
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
} 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 = deduceWebApplicationType();
//添加初始化器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//添加监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//定位main方法所在类,并做记录
this.mainApplicationClass = deduceMainApplicationClass();
}
SpringBoot应用有三种类型:
源码4-来自:WebApplicationType
public enum WebApplicationType { NONE,SERVLET,REACTIVE }
- NONE:应用不是web应用,启动时不必启动内置的服务器程序
- SERVLET:这是一个基于Servlet的web应用,需要开启一个内置的Servlet容器(web服务器)
- REACTIVE:这是一个反应式web应用,需要开启一个内置的反应式web服务器
三者的判断条件如下
源码5-来自:SpringApplication
private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
上面的方法中涉及四个常量:
源码6-来自:SpringApplication
private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.reactive.DispatcherHandler"; private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet"; private static final String JERSEY_WEB_ENVIRONMENT_CLASS = "org.glassfish.jersey.server.ResourceConfig"; private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
至于代码很是简单。
然后是设置初始化器,结合之前的讲述在SSM架构的web应用中也拥有初始化器,这两个代表的是同一个概念,不同于之前的处理方式,这里仅仅是将初始化器找到并设置到initializers属性中。
我们来看看它是如何获取这些初始化器的:
源码7-来自:SpringApplication、SpringFactoriesLoader
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 = 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;
} 下面来自:SpringFactoriesLoader public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
} private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//首先尝试从缓存获取
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
} try
//从META-INF/spring.factories文件中获取配置的内容
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
//添加到缓存备用
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
由此可见,初始化器是从META-INF/spring.factories文件中获取到的,那么我们就可以自建该目录文件进行添加,前提是需要自定义初始化器。
还有这里只有在第一次调用时需要从文件读取,第二次就可以从缓存获取,哪怕需要的不是初始化器的内容,因为第一次的时候就会将所有的配置内容全部获取并解析保存到缓存备用。这也就为下一步设置监听器这一步省下了不少时间。
最后一步就是将main方法所在类做个记录。
源码8-来自:SpringApplication
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;
}
通过新建一个运行时异常的方式获取方法调用栈,在栈中搜索方法名为main的方法所在的类。
如果我们想要获取方法的调用栈也可以采用这种方式,使用异常来获取调用栈。
完成SpringApplication实例的创建之后,直接调用其run方法,执行应用启动。
源码9-来自:SpringApplication
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//配置系统属性headless,默认为true
configureHeadlessProperty();
//获取所有的运行时监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();//启动监听器
try {
//封装自定义参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//根据参数配置环境environment
//如果是web项目则创建的是StandardServletEnvironment,否则是StandardEnvironment
//然后配置属性和profile
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//设置是否忽略BeanInfo,默认为true
configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = printBanner(environment);
//创建ApplicationContext
//如果是Servlet web项目则创建AnnotationConfigEmbeddedWebApplicationContext,如果是Reactive web
//项目则创建AnnotationConfigReactiveWebServerApplicationContext,否则AnnotationConfigApplicationContext
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);//容器和应用启用之后,callRunner之前执行
//容器启动完成之后回调runner,包括:ApplicationRunner和CommandLineRunner
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;
}
这是整个容器启动的所有逻辑入口,参照注释即可理解,我们重点关注prepareContext方法,这个方法是在容器刷新之前的预初始化操作:
源码10-来自:SpringApplication
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//设置environment到上下文
context.setEnvironment(environment);
//设置BeanName生成器
//设置资源加载器或者类加载器
postProcessApplicationContext(context);
//执行初始化器
applyInitializers(context);
listeners.contextPrepared(context);//执行监听器的contextPrepared操作
//配置日志信息
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
} // Add boot specific singleton beans
//注册单例(springApplicationArguments,springBootBanner)
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
} // Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载Bean到容器
load(context, sources.toArray(new Object[0]));
//执行监听器的contextLoaded操作
listeners.contextLoaded(context);
}
重点关注下load方法,他的目的是加载Bean定义:
源码11-来自:SpringApplication
//为load操作做准备,创建资源读取器扫描器
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug(
"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
//创建BeanDefinition读取器,扫描器(AnnotatedBeanDefinitionReader,XmlBeanDefinitionReader,
//GroovyBeanDefinitionReader,ClassPathBeanDefinitionScanner)
BeanDefinitionLoader loader = createBeanDefinitionLoader(
getBeanDefinitionRegistry(context), sources);
//配置BeanName生成器,资源加载器,环境参数到读取器和扫描器中
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
//执行Bean的加载
loader.load();
} //统计加载Bean的数量
public int load() {
int count = 0;
for (Object source : this.sources) {
count += load(source);
}
return count;
} //针对不同的情况加载bean资源
private int load(Object source) {
Assert.notNull(source, "Source must not be null");
//
if (source instanceof Class<?>) {
return load((Class<?>) source);
}
//Resource资源可能是XML定义的Bean资源或者groovy定义的Bean资源
if (source instanceof Resource) {
return load((Resource) source);
}
//Packet资源一般是用于注解定义的Bean资源,需要扫描器扫描包
if (source instanceof Package) {
return load((Package) source);
}
//
if (source instanceof CharSequence) {
return load((CharSequence) source);
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}
针对不同的资源进行加载。
下面就要回到run方法中的重点操作refreshContext(context)了:
源码12-来自:SpringApplication
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
} protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
这里也到了执行refresh()方法的时候了,下面我们就要看看这个refresh了。
Spring基础系列-容器启动流程(2)的更多相关文章
- Spring基础系列-容器启动流程(1)
原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9870339.html 概述 我说的容器启动流程涉及两种情况,SSM开发模式和Spri ...
- Spring基础系列-AOP源码分析
原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9560803.html 一.概述 Spring的两大特性:IOC和AOP. AOP是面向切 ...
- Spring基础系列--AOP织入逻辑跟踪
原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9619910.html 其实在之前的源码解读里面,关于织入的部分并没有说清楚,那些前置.后 ...
- Spring基础系列-Spring事务不生效的问题与循环依赖问题
原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9476550.html 一.提出问题 不知道你是否遇到过这样的情况,在ssm框架中开发we ...
- Spring基础系列-Web开发
原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9996902.html SpringBoot基础系列-web开发 概述 web开发就是集成 ...
- spring boot, 容器启动后执行某操作
常有在spring容器启动后执行某些操作的需求,现做了一个demo的实现,做一下记录,也希望可以给需要的同学提供参考. 1.spring启动后,以新线程执行后续需要的操作,所以执行类实现Runnabl ...
- Spring Boot 应用程序启动流程分析
SpringBoot 有两个关键元素: @SpringBootApplicationSpringApplication 以及 run() 方法 SpringApplication 这个类应该算是 Sp ...
- SpringBean容器启动流程+Bean的生命周期【附源码】
如果对SpringIoc与Aop的源码感兴趣,可以访问参考:https://javadoop.com/,十分详细. 目录 Spring容器的启动全流程 Spring容器关闭流程 Bean 的生命周期 ...
- dubbo源码学习(一)dubbo容器启动流程简略分析
最近在学习dubbo,dubbo的使用感觉非常的简单,方便,基于Spring的容器加载配置文件就能直接搭建起dubbo,之前学习中没有养成记笔记的习惯,时间一久就容易忘记,后期的复习又需要话费较长的时 ...
随机推荐
- OpenAL音频库例程
Windows下C++可用的OpenAL demo. 基于alut工具库的OpenAL例程,涵盖了基本的OpenAL指令,对部分作出了注释,并且可以播放(当然得把对应的音频文件放到正确的路径下). # ...
- 通过shell脚本进行数据库操作
#!/bin/bash HOSTNAME="192.168.111.84" #数据库信息 PORT=" USERNAME="root" PASSWOR ...
- 关闭iptables服务及命令行连接wifi及locale设置
Ubuntu系统启动时都会自动启动iptables服务.如果想关闭该服务的自动启动,可以执行: sudo ufw disable 命令行方式连接某个SSID: sudo nmcli d wifi co ...
- 关于Socket.IO的知识点记录
最近因为项目的需要,开始学习nodejs,本着js的那点儿功底,nodejs学习起来还是挺快能上手的.随着深入学习,知道了express框架并那它写了一个小功能,作为一个php程序员哈,在expres ...
- C#中数组、ArrayList和List三者的区别 转
在C#中数组,ArrayList,List都能够存储一组对象,那么这三者到底有什么样的区别呢. 数组 数组在C#中最早出现的.在内存中是连续存储的,所以它的索引速度非常快,而且赋值与修改元素也很简单. ...
- C语言 字符二维数组(多个字符串)探讨 求解
什么是二维字符数组? 二维字符数组中为什么定义字符串是一行一个? “hello world”在C语言中代表什么? 为什么只能在定义时才能写成char a[10]="jvssj" ...
- css格式比较及选择器类型总结
在前端入门的前三天把网页制作过程中常用的一些标签和属性都认识和练习了一遍,能够做出简单模块的框架.就像老师说的网页制作就像建一栋大楼,html是砖和水泥,css是精装,js是完善各个功能.现在就开始进 ...
- linux 值安装yum包
1. 创建文件,挂载 rhel7-repo-iso [root@rhel7 ~]# mkdir /media/rhel7-repo-iso [root@rhel7 ~]# mount /dev/cd ...
- 【百度杯】ctf夺旗大战,16万元奖励池等你拿
寻找安全圈内最会夺flag的CTF职业玩家,将以个人方式参与夺旗,完全凭借个人在CTF中的技艺及造诣获得奖金回报. 周末少打一局LOL,玩一玩CTF也能挣个万元零花钱! **比赛时间: 9月至17年3 ...
- Python super() 函数的概念和例子
概念: super() 函数是用于调用父类(超类)的一个方法. super 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO).重 ...