2019独角兽企业重金招聘Python工程师标准>>>

1. 环境,程序入口

@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}@RestController
public class RootController {public static final String PATH_ROOT = "/";@RequestMapping(PATH_ROOT)public String welcome() {return "Welcome!";}}

这里的几行代码就是Spring BootWeb程序,入口是main方法,当访问url的path部分为”/”时,返回字符串”Welcome!”。

解释:在这个main方法中,调用了SpringApplication的静态run方法,并将Application类对象和main方法的参数args作为参数传递了进去。

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

然后是一个使用了两个Spring注解的RootController类,我们在main方法中,没有直接使用这个类。

2. 构造SpringApplication对象

public SpringApplication(Object... sources) {initialize(sources);
}private void initialize(Object[] sources) {// 为成员变量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();
}

构造函数中调用initialize方法,初始化SpringApplication对象的成员变量sources,webEnvironment,initializers,listeners,mainApplicationClass。sources的赋值比较简单,就是我们传给SpringApplication.run方法的参数。

2.1 首先是webEnvironment

private boolean webEnvironment; private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext" };private void initialize(Object[] sources) {...// 为成员变量webEnvironment赋值this.webEnvironment = deduceWebEnvironment();...
}private boolean deduceWebEnvironment() {for (String className : WEB_ENVIRONMENT_CLASSES) {if (!ClassUtils.isPresent(className, null)) {return false;}}return true;
}

webEnvironment是一个boolean,该成员变量用来表示当前应用程序是不是一个Web应用程序。那么怎么决定当前应用程序是否Web应用程序呢,是通过在classpath中查看是否存在WEB_ENVIRONMENT_CLASSES这个数组中所包含的类,如果存在那么当前程序即是一个Web应用程序,反之则不然。

2.2 成员变量initializers:

initializers是一个ApplicationContextInitializer类型对象的集合。 所以,ApplicationContextInitializer是一个可以用来初始化ApplicationContext的接口。

private List<ApplicationContextInitializer<?>> initializers;private void initialize(Object[] sources) {...// 为成员变量initializers赋值setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));...
}public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {this.initializers = new ArrayList<ApplicationContextInitializer<?>>();this.initializers.addAll(initializers);
}

关键是调用getSpringFactoriesInstances(ApplicationContextInitializer.class),来获取ApplicationContextInitializer类型对象的列表。

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

首先通过调用SpringFactoriesLoader.loadFactoryNames(type, classLoader)来获取所有Spring Factories的名字,然后调用createSpringFactoriesInstances方法根据读取到的名字创建对象。最后会将创建好的对象列表排序并返回。

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {String factoryClassName = factoryClass.getName();try {Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_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(factoryClassNames)));}return result;}catch (IOException ex) {throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);}
}

从一个名字叫spring.factories的资源文件中,读取key为org.springframework.context.ApplicationContextInitializer的value。

spring.factories的部分内容如下:

以下内容摘自spring-boot-1.3.3.RELEASE.jar中的资源文件META-INF/spring.factories# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer

接下来会调用createSpringFactoriesInstances来创建ApplicationContextInitializer实例。

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.getConstructor(parameterTypes);T instance = (T) constructor.newInstance(args);instances.add(instance);}catch (Throwable ex) {throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);}}return instances;
}

SpringApplication对象的成员变量initalizers就被初始化为,ConfigurationWarningsApplicationContextInitializer,ContextIdApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer这四个类的对象组成的list。

ApplicationContextInitializer说明如图:

2.3 成员变量listeners

以下代码摘自:org.springframework.boot.SpringApplicationprivate List<ApplicationListener<?>> listeners;private void initialize(Object[] sources) {...// 为成员变量listeners赋值setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));...
}public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {this.listeners = new ArrayList<ApplicationListener<?>>();this.listeners.addAll(listeners);
}

listeners成员变量,是一个ApplicationListener<?>类型对象的集合。可以看到获取该成员变量内容使用的是跟成员变量initializers一样的方法,只不过传入的类型从ApplicationContextInitializer.class变成了ApplicationListener.class。

下图画出了加载的ApplicationListener,并说明了他们的作用。

2.4 mainApplicationClass

以下代码摘自:org.springframework.boot.SpringApplicationprivate Class<?> mainApplicationClass;private void initialize(Object[] sources) {...// 为成员变量mainApplicationClass赋值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;
}

在deduceMainApplicationClass方法中,通过获取当前调用栈,找到入口方法main所在的类,并将其复制给SpringApplication对象的成员变量mainApplicationClass。例子中mainApplicationClass是自己编写的Application类。

SpringApplication对象的run方法

经过上面的初始化过程,我们已经有了一个SpringApplication对象,根据SpringApplication类的静态run方法一节中的分析,接下来会调用SpringApplication对象的run方法。我们接下来就分析这个对象的run方法。

以下代码摘自:org.springframework.boot.SpringApplicationpublic ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;configureHeadlessProperty();SpringApplicationRunListeners listeners = getRunListeners(args);listeners.started();try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);context = createAndRefreshContext(listeners, applicationArguments);afterRefresh(context, applicationArguments);listeners.finished(context, null);stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}return context;}catch (Throwable ex) {handleRunFailure(context, listeners, ex);throw new IllegalStateException(ex);}
}可变个数参数args就是我们整个应用程序的入口main方法的参数,在我们的例子中,参数个数为零。StopWatch是来自org.springframework.util的工具类,可以用来方便的记录程序的运行时间。

SpringApplication对象的run方法创建并刷新ApplicationContext,算是开始进入正题了。下面按照执行顺序,介绍该方法所做的工作。

headless模式

以下代码摘自:org.springframework.boot.SpringApplicationprivate static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
private boolean headless = true;public ConfigurableApplicationContext run(String... args) {...//设置headless模式configureHeadlessProperty();...
}private void configureHeadlessProperty() {System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

实际上是就是设置系统属性java.awt.headless,在我们的例子中该属性会被设置为true,因为我们开发的是服务器程序,一般运行在没有显示器和键盘的环境。

SpringApplicationRunListeners

以下代码摘自:org.springframework.boot.SpringApplicationpublic ConfigurableApplicationContext run(String... args) {...SpringApplicationRunListeners listeners = getRunListeners(args);listeners.started();/*** 创建并刷新ApplicationContext* context = createAndRefreshContext(listeners, applicationArguments); **/listeners.finished(context, null);...
}private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

run方法中,加载了一系列SpringApplicationRunListener对象,在创建和更新ApplicationContext方法前后分别调用了listeners对象的started方法和finished方法, 并在创建和刷新ApplicationContext时,将listeners作为参数传递到了createAndRefreshContext方法中,以便在创建和刷新ApplicationContext的不同阶段,调用listeners的相应方法以执行操作。所以,所谓的SpringApplicationRunListeners实际上就是在SpringApplication对象的run方法执行的不同阶段,去执行一些操作,并且这些操作是可配置的。

同时,可以看到,加载SpringApplicationRunListener时,使用的是跟加载ApplicationContextInitializer和ApplicationListener时一样的方法。那么加载了什么,就可以从spring.factories文件中看到了:

以下内容摘自spring-boot-1.3.3.RELEASE.jar中的资源文件META-INF/spring.factories# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

可以看到,在我们的例子中加载的是org.springframework.boot.context.event.EventPublishingRunListener。

SpringApplicationRunListener究竟做了点什么工作了?

以下代码摘自:org.springframework.boot.context.event.EventPublishingRunListenerpublic EventPublishingRunListener(SpringApplication application, String[] args) {this.application = application;this.args = args;this.multicaster = new SimpleApplicationEventMulticaster();for (ApplicationListener<?> listener : application.getListeners()) {this.multicaster.addApplicationListener(listener);}
}@Override
public void started() {publishEvent(new ApplicationStartedEvent(this.application, this.args));
}@Override
public void environmentPrepared(ConfigurableEnvironment environment) {publishEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args,environment));
}@Override
public void contextPrepared(ConfigurableApplicationContext context) {registerApplicationEventMulticaster(context);
}@Override
public void contextLoaded(ConfigurableApplicationContext context) {for (ApplicationListener<?> listener : this.application.getListeners()) {if (listener instanceof ApplicationContextAware) {((ApplicationContextAware) listener).setApplicationContext(context);}context.addApplicationListener(listener);}publishEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}@Override
public void finished(ConfigurableApplicationContext context, Throwable exception) {publishEvent(getFinishedEvent(context, exception));
}

EventPublishingRunListener在对象初始化时,将SpringApplication对象的成员变量listeners全都保存下来,然后在自己的public方法被调用时,发布相应的事件,或执行相应的操作。可以说这个RunListener是在SpringApplication对象的run方法执行到不同的阶段时,发布相应的event给SpringApplication对象的成员变量listeners中记录的事件监听器。

下图是SpringApplicationRunListeners相关的类结构

同时,可以看到,加载SpringApplicationRunListener时,使用的是跟加载ApplicationContextInitializer和ApplicationListener时一样的方法。那么加载了什么,就可以从spring.factories文件中看到了:

IDEA真的是开发人员的利器!!!

参考:http://www.importnew.com/24875.html

转载于:https://my.oschina.net/ChinaHaoYuFei/blog/1605882

Spring Boot相关知识(二) Spring Boot项目启动的执行顺序和原理相关推荐

  1. spring配置项目启动时执行

    首先在配置文件中添加配置 <!-- 配置项目启动时执行  init方法--> <bean id="smsmanage" class="com.longr ...

  2. Spring AOP之坑:完全搞清楚advice的执行顺序

    目录 文章目录 目录 AOP的核心概念 模拟aspect advice的执行过程 同一aspect,不同advice的执行顺序 不同aspect,advice的执行顺序 同一aspect,相同advi ...

  3. java 项目启动初始化_Spring项目启动时执行初始化方法

    一.applicationContext.xml配置bean init-method="initKeyWord"> classpath:sensitive-word.xml ...

  4. Springboot 项目启动后执行某些自定义代码

    Springboot 项目启动后执行某些自定义代码 Springboot给我们提供了两种"开机启动"某些方法的方式:ApplicationRunner和CommandLineRun ...

  5. 为什么当项目启动后执行两次过滤器 再进行访问资源时执行一次过滤器

    为什么当项目启动后执行两次过滤器 再进行访问资源时执行一次过滤器 粗线的理解,启动项目后进行了两次请求,访问两次过滤器.访问资源时只进行一次请求,所以访问一次过滤器. 于是为了更加清楚地看清在启动项目 ...

  6. java启动时执行_java怎么实现项目启动时执行指定方法

    本文实例为大家共享了java项目启动时执行指定方法,供大家参考,详细内容如下 想到的就是监听步骤如下: 1.配置web.xml com.listener.InitListener 2.编写InitLi ...

  7. Springboot项目启动前执行数据库初始化脚本

    背景:项目里面遇到了要在springboot项目启动前做数据库初始化的需求.总结一下几种方案: 1.使用flywaydb,启动工程的时候同时初始化脚本.集成倒是不难,主要是要解决bean的顺序加载问题 ...

  8. WEB API 系列(二) Filter的使用以及执行顺序

    在WEB Api中,引入了面向切面编程(AOP)的思想,在某些特定的位置可以插入特定的Filter进行过程拦截处理.引入了这一机制可以更好地践行DRY(Don't Repeat Yourself)思想 ...

  9. Java项目启动时执行指定方法的几种方式

    版权声明:本文为博主原创文章,未经博主允许不得转载.博客源地址为zhixiang.org.cn https://blog.csdn.net/myFirstCN/article/details/8175 ...

最新文章

  1. PXE实现Linux的自动安装
  2. 理解Android系统的进程间通信原理------RPC机制
  3. DLL中导出函数的声明有两种方式
  4. web报告工具FineReport在使用方法和解决方案常见错误遇到(一)
  5. android listadepter 释放资源,SimpleAdepter浅谈
  6. mysql8.0版本的服务器名称_云服务器Mysql安装配置
  7. 11-6渐变的用途和设定技巧
  8. oracle零碎要点---ip地址问题,服务问题,系统默认密码问题
  9. 【2】puppet笔记 - package、service、user资源
  10. html把切片变成div,关联网页自动切片生成Div+Css软件
  11. MATLAB图像处理
  12. SqlServer——正则表达式
  13. 微信小程序实践——实验2天气查询小程序
  14. 小技巧 - 查询微信被谁投诉方法技巧
  15. python基础系统学习整理,自学者可以参考的不二笔记
  16. 搜索引擎蜘蛛抓取不到网站内容页面的原因总结
  17. git tag 打标签(我看过最透彻的文章)
  18. VC2005操作Word
  19. ThreadPoolExecutor 线程池的七个参数
  20. 亿级用户体量,千万级日活用户,《王者荣耀》高并发背后的故事!

热门文章

  1. python截取html图片大小,Python打开html文件,截取屏幕截图,裁剪并保存为图像
  2. 公司--超级链接跳转和表单提交,删除的表单提交和删除失败的提示信息
  3. 自考计算机本科学校好,自考本科的难度跟选择的专业有关吗?过来人:有很大的关系...
  4. pycharm 远程调试图文_Pycharm配置远程调试的图文步骤
  5. 实验研究信标无线电能输出功率的因素
  6. 测试可编程波形发生器 AD9833
  7. python中继承是什么意思_如何理解Python中的继承?python入门
  8. integer比较_Java整数缓存Integer.valueOf(127)==Integer.valueOf(127)为True
  9. linux rabbitmq安装包,Linux安装RabbitMQ
  10. addhandler php5-script php,htaccess和AddType/Addhandler