Spring Boot 2.x 启动全过程源码分析(上)入口类剖析

转载自   Spring Boot 2.x 启动全过程源码分析(上)入口类剖析

Spring Boot 的应用教程我们已经分享过很多了,今天来通过源码来分析下它的启动过程,探究下 Spring Boot 为什么这么简便的奥秘。

本篇基于 Spring Boot 2.0.3 版本进行分析,阅读本文需要有一些 Java 和 Spring 框架基础,如果还不知道 Spring Boot 是什么,建议先看下我们的 Spring Boot 教程

Spring Boot 的入口类

@SpringBootApplication
public class SpringBootBestPracticeApplication {public static void main(String[] args) {SpringApplication.run(SpringBootBestPracticeApplication.class, args);}
}

做过 Spring Boot 项目的都知道,上面是 Spring Boot 最简单通用的入口类。入口类的要求是最顶层包下面第一个含有 main 方法的类,使用注解 @SpringBootApplication 来启用 Spring Boot 特性,使用 SpringApplication.run 方法来启动 Spring Boot 项目。

来看一下这个类的 run 方法调用关系源码:

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);
}

第一个参数 primarySource:加载的主要资源类

第二个参数 args:传递给应用的应用参数

先用主要资源类来实例化一个 SpringApplication 对象,再调用这个对象的 run 方法,所以我们分两步来分析这个启动源码。

SpringApplication 的实例化过程

 

接着上面的 SpringApplication 构造方法进入以下源码:

 
public SpringApplication(Class<?>... primarySources) {this(null, primarySources);
}public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {// 1、资源初始化资源加载器为 nullthis.resourceLoader = resourceLoader;// 2、断言主要加载资源类不能为 null,否则报错Assert.notNull(primarySources, "PrimarySources must not be null");// 3、初始化主要加载资源类集合并去重this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));// 4、推断当前 WEB 应用类型this.webApplicationType = deduceWebApplicationType();// 5、设置应用上线文初始化器setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));      // 6、设置监听器            setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));// 7、推断主入口应用类this.mainApplicationClass = deduceMainApplicationClass();}

可知这个构造器类的初始化包括以下 7 个过程。

1、资源初始化资源加载器为 null

this.resourceLoader = resourceLoader;

2、断言主要加载资源类不能为 null,否则报错

Assert.notNull(primarySources, "PrimarySources must not be null");

3、初始化主要加载资源类集合并去重

this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

4、推断当前 WEB 应用类型

this.webApplicationType = deduceWebApplicationType();

来看下 deduceWebApplicationType 方法和相关的源码:

private WebApplicationType deduceWebApplicationType() {if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {return WebApplicationType.REACTIVE;}for (String className : WEB_ENVIRONMENT_CLASSES) {if (!ClassUtils.isPresent(className, null)) {return WebApplicationType.NONE;}}return WebApplicationType.SERVLET;
}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[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext" };      public enum WebApplicationType {/*** 非 WEB 项目*/NONE,/*** SERVLET WEB 项目*/SERVLET,/*** 响应式 WEB 项目*/REACTIVE}

这个就是根据类路径下是否有对应项目类型的类推断出不同的应用类型。

5、设置应用上线文初始化器

setInitializers((Collection) getSpringFactoriesInstances(

ApplicationContextInitializer.class));

ApplicationContextInitializer 的作用是什么?源码如下。

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {/*** Initialize the given application context.* @param applicationContext the application to configure*/void initialize(C applicationContext);}

用来初始化指定的 Spring 应用上下文,如注册属性资源、激活 Profiles 等。

来看下 setInitializers 方法源码,其实就是初始化一个 ApplicationContextInitializer 应用上下文初始化器实例的集合。

public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {this.initializers = new ArrayList<>();this.initializers.addAll(initializers);}

再来看下这个初始化 getSpringFactoriesInstances 方法和相关的源码:

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 duplicatesSet<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));List<T> instances = createSpringFactoriesInstances(type, parameterTypes,classLoader, args, names);AnnotationAwareOrderComparator.sort(instances);return instances;
}

设置应用上下文初始化器可分为以下 5 个步骤。

5.1)获取当前线程上下文类加载器

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

5.2)获取 ApplicationContextInitializer 的实例名称集合并去重

Set<String> names = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader));

loadFactoryNames 方法相关的源码如下:

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {String factoryClassName = factoryClass.getName();return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());}public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {MultiValueMap<String, String> result = cache.get(classLoader);if (result != null) {return result;}try {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 文件解析并获取 ApplicationContextInitializer 接口的所有配置的类路径名称。

spring-boot-autoconfigure-2.0.3.RELEASE.jar!/META-INF/spring.factories 的初始化器相关配置内容如下:

  1. # Initializers

  2. org.springframework.context.ApplicationContextInitializer=\

  3. org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\

  4. org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

5.3)根据以上类路径创建初始化器实例列表

List<T> instances = createSpringFactoriesInstances(type, parameterTypes,classLoader, args, names);private <T> List<T> createSpringFactoriesInstances(Class<T> type,Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,Set<String> names) {List<T> instances = new ArrayList<>(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;
}

5.4)初始化器实例列表排序

AnnotationAwareOrderComparator.sort(instances);

5.5)返回初始化器实例列表

return instances;

6、设置监听器

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

ApplicationListener 的作用是什么?源码如下。

@FunctionalInterfacepublic interface ApplicationListener<E extends ApplicationEvent> extends EventListener {/*** Handle an application event.* @param event the event to respond to*/void onApplicationEvent(E event);}

看源码,这个接口继承了 JDK 的 java.util.EventListener 接口,实现了观察者模式,它一般用来定义感兴趣的事件类型,事件类型限定于 ApplicationEvent 的子类,这同样继承了 JDK 的 java.util.EventObject 接口。

设置监听器和设置初始化器调用的方法是一样的,只是传入的类型不一样,设置监听器的接口类型为: getSpringFactoriesInstances,对应的 spring-boot-autoconfigure-2.0.3.RELEASE.jar!/META-INF/spring.factories 文件配置内容请见下方。

  1. # Application Listeners

  2. org.springframework.context.ApplicationListener=\

  3. org.springframework.boot.autoconfigure.BackgroundPreinitializer

可以看出目前只有一个 BackgroundPreinitializer 监听器。

7、推断主入口应用类

this.mainApplicationClass = deduceMainApplicationClass();
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 方法,作者很快写完过两天就发布,扫码关注下面的公众号 "Java技术栈" 即可获取推送更新。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/325502.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

永远不要、不要、不要、不要放弃

Never, never, never, never give up. 永远不要、不要、不要、不要放弃。今天来写一下18级学生们的状态吧&#xff0c;最近主要是解决了1班的三大问题&#xff0c;第一&#xff0c;上机测试问题。第二&#xff0c;周一到四期间学习任务安排问题。第三&#xff0c;学习氛围的进一…

JS的时间定时器

<script>var t null;t setTimeout(time, 1000); //開始运行function time() {clearTimeout(t); //清除定时器dt new Date();var y dt.getFullYear();var mt dt.getMonth() 1;var day dt.getDate();var h dt.getHours(); //获取时var m dt.getMinutes(); //获取分…

Android隐藏状态栏和标题栏

requestWindowFeature(Window.FEATURE_NO_TITLE);// 隐藏标题栏getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);// 隐藏状态栏setContentView(R.layout.activity_ceshi);

微软正式发布XAML Standard与.NET Standard 2.0:现已提供下载

微软在本月早些时候召开的 Build 2017 开发者大会上的披露的 XAML Standard 和 .NET Standard 2.0&#xff0c;现已正式发布。新工具旨在为开发者们带来“基于同一标准的跨平台 XAML 语言结构”&#xff08;基于 UWP 和 Xamarin.Forms&#xff09;&#xff0c;以及基于社区反馈…

Spring Boot 2.X 来临,本文将带你起飞

转载自 Spring Boot 2.X 来临&#xff0c;本文将带你起飞 当前互联网技术盛行&#xff0c;以Spring 框架为主导的Java 互联网技术成了主流&#xff0c;而基于Spring 技术衍生出来的Spring Boot&#xff0c;采用了“约定优于配置”的思想&#xff0c;极大地简化了Spring 框架…

时间胶囊——给未来的留言板

时间胶囊”是一个给未来的留言板&#xff0c;你可以为自己&#xff0c;朋友&#xff0c;爱人&#xff0c;家人&#xff0c;或者任何人留下你现在想对他们说的话、图片&#xff0c;将来某一天&#xff0c;他们将来这里打开“时间胶囊”读到你的留言&#xff01;那么“时间胶囊”…

Echarts五步法加初体验

使用步骤&#xff1a; 引入echarts 插件文件到html页面中准备一个具备大小的DOM容器 <div id"main" style"width: 600px;height:400px;"></div>初始化echarts实例对象 var myChart echarts.init(document.getElementById(main));指定配置项…

Android软键盘弹出时,覆盖布局,不是把布局顶上去的解决方法

方法一&#xff1a; 在你的activity中的oncreate中setContentView之前写上这个代码 getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);方法二&#xff1a; 如果想activity页面整体上移&#xff0c;在项目的AndroidManifest.xml文件中界面对应…

win7禁用其他软件只启用自定义软件的方法

gpedit.msc 用户配置里的管理模板→系统→右侧窗口里的“只运行。。。。”或“不运行。。。。” 然后输入&#xff1a; notepad.exe winword.exe powerpnt.exe excel.exe typeeasy.exe mmc.exe 360se.exe Hbuilde.exe

深入理解C#:编程技巧总结(一)

以下总结参阅了&#xff1a;MSDN文档、《C#高级编程》、《C#本质论》、前辈们的博客等资料&#xff0c;如有不正确的地方&#xff0c;请帮忙及时指出&#xff01;以免误导&#xff01; 1..实现多态性的两种方式&#xff1a;继承抽象类、实现接口 其实就是协变的应用&#xff…

史上最全 69 道 Spring 面试题和答案

转载自 史上最全 69 道 Spring 面试题和答案 目录 Spring 概述 依赖注入 Spring beans Spring注解 Spring数据访问 Spring面向切面编程&#xff08;AOP&#xff09; Spring MVC Spring 概述 1. 什么是spring? Spring 是个java企业级应用的开源开发框架。Spring主要…

Android 隐藏底部三个虚拟按键

工具类中使用 // 隐藏底部的虚拟按键 方法一 滑动屏幕 可重新显示出来public static void hideBottomUIMenu(Activity activity) {//隐藏虚拟按键&#xff0c;并且全屏if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) { // lower apiView v …

JS中对象创建的五中方式

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>01_Object构造函数模式</title> </head> <body> <!-- 方式一: Object构造函数模式* 套路: 先创建空Object对象, 再动态添加属性/…

使用中控指纹采集器开发指纹识别案例V1.0

这两天正好有点琐碎的时间&#xff0c;就将两年前未开发完毕的指纹识别项目翻出来继续写了写。 运行环境&#xff1a;  中控指纹采集器  Win10操作系统  .netframework4.0  Sqlserver2008及以上 源码已经上传到微信公众号【雄雄的小课堂】中&#xff0c;回复“指纹识别…

在CentOS上使用Jexus托管运行 ZKEACMS

ZKEACMS Core 是基于 .net core 开发的&#xff0c;可以在 windows, linux, mac 上跨平台运行&#xff0c;接下来我们来看看如何在 CentOS 上使用Jexus托管运行 ZKEACMS&#xff0c;通常我们在Linux部署ASP.NET Core应用&#xff0c;按照微软的官方文档&#xff0c;我们通常需要…

跟我学 Java 8 新特性之 Stream 流(六)收集

转载自 跟我学 Java 8 新特性之 Stream 流&#xff08;六&#xff09;收集 我们前面的五篇文章基本都是在说将一个集合转成一个流&#xff0c;然后对流进行操作&#xff0c;其实这种操作是最多的&#xff0c;但有时候我们也是需要从流中收集起一些元素&#xff0c;并以集合的…

android解决工具类中没有context中的问题

有时我们需要在一个工具类中用到上下文context。当然有一个做法就是使用传递context的方法&#xff0c;当我们需要用全局context的时候&#xff0c;该怎么办呢&#xff1f; 其实我们应用启动的时候会启动Application这个类&#xff0c;这个类是在AndroidManifest.xml文件里其实…

中控指纹采集器开发指纹识别项目(说明)

历史指纹识别相关开发版本&#xff1a;指纹识别开发1.0&#xff0c;开发时间&#xff1a;2018-01-04 指纹识别开发2.0&#xff0c;开发时间&#xff1a;2018-01-04指纹识别开发3.0&#xff0c;开发时间&#xff1a;2020-01-06可以从时间上看的出来&#xff0c;在2018年1月4日&a…

JS生成x到y的随机数

/** Math.random()* - 可以用来生成一个0-1之间的随机数* - 生成一个0-10的随机数* - 生成一个0-x之间的随机数* Math.round(Math.random()*x)* * - 生成一个1-10* - 生成一个x-y之间的随机数* Math.round(Math.random()*(y-x)x)*//*for(var i0 ; i<100 ; i){//con…

MSSQL-Scripter,一个新的生成T-SQL脚本的SQL Server命令行工具

这里向大家介绍一个新的生成T-SQL脚本的SQL Server命令行工具&#xff1a;mssql-scripter。它支持在SQL Server、Azure SQL DB以及Azure SQL DW中为数据库生成CREATE和INSERT T-SQL脚本。 Mssql-scripter是一个跨平台的命令行工具&#xff0c;功能等同于SQL Server Management…