spring boot 源码解析2-SpringApplication初始化
前⾔
我们⽣成⼀个spring boot 项⽬时,会⾃带⼀个启动类. 代码如下:
@SpringBootApplication public class SpringBootAnalysisApplication { public static void main(String[] args) { SpringApplication.run(SpringBootAnalysisApplication.class, args); } }
就是这么简单的代码,构成了spring boot的世界. 那么代码中只有⼀个@SpringBootApplication 注解 和 调⽤了SpringApplication#run
⽅法.那么我们先来解析SpringApplication的run⽅法.
解析
⾸先调⽤了org.springframework.boot.SpringApplication#run(Object, String...) ⽅法.代码如下:
public static ConfigurableApplicationContext run(Object source, String... args) { return run(new Object[] { source }, args); }
接着调⽤如下代码:
public static ConfigurableApplicationContext run(Object[] sources, String[] args) { return new SpringApplication(sources).run(args); }
可以发现 ⾸先初始化了SpringApplication,然后调⽤其实例⽅法:run.
- 在 SpringApplication 的构造器中,调⽤了 initialize ⽅法.
public SpringApplication(Object... sources) { initialize(sources); }
- SpringApplication#initialize⽅法代码如下:
private void initialize(Object[] sources) { if (sources != null && sources.length > 0) { this.sources.addAll(Arrays.asList(sources)); } this.webEnvironment = deduceWebEnvironment(); setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
可以看到做了如下5件事:
- 如果sources⻓度⼤于0的话,加⼊到SpringApplication的sources中,该sources是⼀个LinkedHashSet.
- 调⽤deduceWebEnvironment⽅法判断是否是web环境
- 设置initializers.
- 设置Listeners.
- 设置mainApplicationClass.
- deduceWebEnvironment代码如下:
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" }; private boolean deduceWebEnvironment() { for (String className : WEB_ENVIRONMENT_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return false; } } return true; }
可以发现会调⽤ClassUtils类的isPresent⽅法,检查classpath中是否存在javax.servlet.Servlet类和
org.springframework.web.context.ConfigurableWebApplicationContext类,如果存在的话,返回true.否则返回false.
- 在设置Initializers时⾸先调⽤getSpringFactoriesInstances⽅法加载ApplicationContextInitializer.然后直接赋值给initializers.代码如下:
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) { return getSpringFactoriesInstances(type, new Class<?>[] {}); }
转⽽调⽤如下代码:
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates // 使⽤Set保存names来避免重复元素 Set<String> names = new LinkedHashSet<String>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 根据names来进⾏实例化 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); // 对实例进⾏排序 AnnotationAwareOrderComparator.sort(instances); return instances; }
该⽅法逻辑如下:
- ⾸先获得ClassLoader.
- 调⽤SpringFactoriesLoader#loadFactoryNames进⾏加载,然后放⼊到LinkedHashSet进⾏去重.
- 调⽤createSpringFactoriesInstances进⾏初始化
- 排序
其中SpringFactoriesLoader#loadFactoryNames代码如下:
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); try { Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURC E_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); List<String> result = new ArrayList<String>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); String factoryClassNames = properties.getProperty(factoryClassName); result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassN ames))); } return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
逻辑如下:
- 获得factoryClassName,对于当前来说factoryClassName =org.springframework.context.ApplicationContextInitializer.
- 通过传⼊的classLoader加载META-INF/spring.factories⽂件.
- 通过调⽤PropertiesLoaderUtils#loadProperties将其转为Properties.
- 获得factoryClassName对应的值进⾏返回.
对于当前来说,由于我们只加⼊了spring-boot-starter-web的依赖,因此会加载如下的配置: - 在spring-boot/META-INF/spring.factories中.org.springframework.context.ApplicationContextInitializer值如下:
org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\ org.springframework.boot.context.ContextIdApplicationContextInitializer,\ org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\ org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer
- 在spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
中.org.springframework.context.ApplicationContextInitializer值如下:
org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
因此会加载6个.
SpringApplication#createSpringFactoriesInstances⽅法如下:
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { List<T> instances = new ArrayList<T>(names.size()); for (String name : names) { try { Class<?> instanceClass = ClassUtils.forName(name, classLoader); Assert.isAssignable(type, instanceClass); Constructor<?> constructor = instanceClass .getDeclaredConstructor(parameterTypes); T instance = (T) BeanUtils.instantiateClass(constructor, args); instances.add(instance); } catch (Throwable ex) { throw new IllegalArgumentException( "Cannot instantiate " + type + " : " + name, ex); } } return instances; }
逻辑如下:遍历传⼊的names,也就是之前通过SpringFactoriesLoader加载的类名.通过遍历,依次调⽤其构造器进⾏初始化.加⼊到
instances.然后进⾏返回.
对于当前场景来说:
ConfigurationWarningsApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer
初始化没有做任何事.
ContextIdApplicationContextInitializer在初始化时.会获得spring boot的应⽤名.搜索路径如下:
- spring.application.name
- vcap.application.name
- spring.config.name
- 如果都没有配置的话,返回application.
代码如下:
private static final String NAME_PATTERN = "${spring.application.name:${vcap.application.name:${s pring.config.name:application}}}"; public ContextIdApplicationContextInitializer() { this(NAME_PATTERN); } public ContextIdApplicationContextInitializer(String name) { this.name = name; }
- 设置SpringApplication#setListeners时,还是同样的套路.调⽤getSpringFactoriesInstances加载META-INF/spring.factories中配置
的org.springframework.context.ApplicationListener. 对于当前来说.加载的类如下:
org.springframework.context.ApplicationListener=\ org.springframework.boot.ClearCachesApplicationListener,\ org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.config.AnsiOutputApplicationListener,\ org.springframework.boot.context.config.ConfigFileApplicationListener,\ org.springframework.boot.context.config.DelegatingApplicationListener,\ org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\ org.springframework.boot.logging.ClasspathLoggingApplicationListener,\ org.springframework.boot.logging.LoggingApplicationListener
这些类在构造器中都没有做任何事.
- 调⽤SpringApplication#deduceMainApplicationClass⽅法.获得应⽤的启动类.该⽅法通过获取当前⽅法调⽤栈,找到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; }
流程图如下:
参考内容:Spring Boot源码解析
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Java NIO 之 Channel(通道)
历史回顾: Java NIO 概览 Java NIO 之 Buffer(缓冲区) 其他高赞文章: 面试中关于Redis的问题看这篇就够了 一文轻松搞懂redis集群原理及搭建与使用 一 Channel(通道)介绍 通常来说NIO中的所有IO都是从 Channel(通道) 开始的。 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。 数据读取和写入操作图示: Java NIO Channel通道和流非常相似,主要有以下几点区别: 通道可以读也可以写,流一般来说是单向的(只能读或者写,所以之前我们用流进行IO操作的时候需要分别创建一个输入流和一个输出流)。 通道可以异步读写。 通道总是基于缓冲区Buffer来读写。 Java NIO中最重要的几个Channel的实现: FileChannel: 用于文件的数据读写 DatagramChannel: 用于UDP的数据读写 SocketChannel: 用于TCP的数据读写,一般是客户端实现 ServerSocketChannel: 允许我们监听TCP链接请求,每个...
- 下一篇
高德地图web端笔记;发送http请求的工具类
1.查询所有电子围栏 package com.skjd.util; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.http.HttpEntity; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- 设置Eclipse缩进为4个空格,增强代码规范
- SpringBoot2整合Redis,开启缓存,提高访问速度
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- MySQL8.0.19开启GTID主从同步CentOS8
- Hadoop3单机部署,实现最简伪集群
- CentOS8编译安装MySQL8.0.19
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2整合Thymeleaf,官方推荐html解决方案