【Spring Boot】SpringBoot-启动流程分析
2017-02-14 10:14
726 查看
SpringBoot核心启动类的SpringApplication流程分析。上一篇用SpringBoot快速搭建并启动了一个WEB服务。有两个点与spring的项目启动有不同,这里我们分析SpringApplication的启动流程。更一篇才是说明关于@SpringBootApplication的SpringBoot注释做说明《SpringBoot-自动配置源码解析》,通过这两个点我们可以对SpringBoot的运行机制做一个比较细致的了解。
这一篇将从SpringApplication.run();开始:
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
[/code]
进入重构run()方法
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
[/code]
从这里可以看到首先创建了一个SpringApplication实例,然后在调用的其run()方法。首先我们先去创建实例这一流程:
2
3
4
5
6
7
8
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12
[/code]
可以看到其调用了initialize()方法
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11
[/code]
从面的代码可以看到初始化过程做了以下几件事情
this.webEnvironment = deduceWebEnvironment();
这一个方法决定创建的是一个WEB应用还是一个SPRING的标准Standalone应用。如果入方法可以看到其是怎么判断的:
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
[/code]
可以看到是根据org.springframework.util.ClassUtils的静态方法去判断classpath里面是否有WEB_ENVIRONMENT_CLASSES包含的类,如果有都包含则返回true则表示启动一个WEB应用,否则返回false启动一个标准Spring的应用。然后通过代码:
2
3
1
2
3
[/code]
可以看到是否启动一个WEB应用就是取决于classpath下是否有javax.servlet.Servlet和
org.springframework.web.context.ConfigurableWebApplicationContext。然后进入下一个阶段:
1
[/code]
这个方法则是初始化classpath下的所有的可用的ApplicationContextInitializer
1
[/code]
这个方法则是初使化classpath下的所有的可用的ApplicationListener
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[/code]
最后找出main方法的全类名并返回其实例并设置到SpringApplication的this.mainApplicationClass完成初始化。然后调用SpringApplication实例的run方法来启动应用,代码如下:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
[/code]
由于过程比较长,则把详细说明放入下一篇博客进行说明,这里只做简要的过程说明run()方法的代码中:蓝色部分为加载SpringApplicationRunListener对整个容器的初始化过程进行监听,这里先不做解释,然后先观察剩下的几行的代码:
2
3
4
5
6
7
1
2
3
4
5
6
7
[/code]
首先是获取启动时传入参数args并初始化为ApplicationArguments对象
SpringApplication.run(Application.class, args);取这里传入值。
然后配置SpringBoot应用的环境:
1
[/code]
下面的则打印标志这个方法不说明,因为没有什么实质性作用,反应到控制台则是以下的效果如果确实想玩玩修改一下标志,那可以在项目的classpath下新建一个banner.txt文件,把想打印到控制台的数据放到文件中即可。比如:
src/main/resources/banner.txt中加入以下内容:
2
3
4
5
6
1
2
3
4
5
6
[/code]
那么启动的时候就可以看到些标识。
然后下面代码就是比较核心的:
2
3
4
1
2
3
4
[/code]
首先是createApplicationContext()方法:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[/code]
可以看出根据这前初始化过程初始化的this.webEnvironment来决定初始化一个什么容器。如果classpath下是否有javax.servlet.Servlet和
org.springframework.web.context.ConfigurableWebApplicationContext类,
则使用DEFAULT_WEB_CONTEXT_CLASS初始化容器,如果不存在则用DEFAULT_CONTEXT_CLASS初始化容器。
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
[/code]
以上是代码指定了容器的类名,最后通过Spring的工具类初始化容器类bean
BeanUtils.instantiate(contextClass);
完成容器的创建工作。然后执行以下的几个步骤完成整个容器的创建与启动以及bean的注入功能。
1
[/code]
以下这一句代码是实现spring-boot-starter-*的自动化配置的关键。包括spring.factories的加载以及自动化配置类的加载以及注入容器的功能可以参考《SpringBoot-自动配置源码解析》
2
1
2
[/code]
至此通过SpringBoot启动的容器已经构造完成。这里忽略了启动流程中的收集各种Listener,创建Environment及Environment的初始化的因为这些地方都是SpringBoot提供的各种扩展点,后面的博客会详细的说明各个扩展点的用处以及扩展的方式。
这一篇将从SpringApplication.run();开始:
/** * Static helper that can be used to run a {@link SpringApplication} from the * specified source using default settings. * @param source the source to load * @param args the application arguments (usually passed from a Java main method) * @return the running {@link ApplicationContext} */ public static ConfigurableApplicationContext run(Object source, String... args) { return run(new Object[] { source }, args); }1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
[/code]
进入重构run()方法
/** * Static helper that can be used to run a {@link SpringApplication} from the * specified sources using default settings and user supplied arguments. * @param sources the sources to load * @param args the application arguments (usually passed from a Java main method) * @return the running {@link ApplicationContext} */ public static ConfigurableApplicationContext run(Object[] sources, String[] args) { return new SpringApplication(sources).run(args); }1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
[/code]
从这里可以看到首先创建了一个SpringApplication实例,然后在调用的其run()方法。首先我们先去创建实例这一流程:
/** * Create a new {@link SpringApplication} instance. The application context will load * beans from the specified sources (see {@link SpringApplication class-level} * documentation for details. The instance can be customized before calling * {@link #run(String...)}. * @param sources the bean sources * @see #run(Object, String[]) * @see #SpringApplication(ResourceLoader, Object...) */ public SpringApplication(Object... sources) { initialize(sources); }1
2
3
4
5
6
7
8
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12
[/code]
可以看到其调用了initialize()方法
@SuppressWarnings({ "unchecked", "rawtypes" }) 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(); }1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11
[/code]
从面的代码可以看到初始化过程做了以下几件事情
this.webEnvironment = deduceWebEnvironment();
这一个方法决定创建的是一个WEB应用还是一个SPRING的标准Standalone应用。如果入方法可以看到其是怎么判断的:
private boolean deduceWebEnvironment() { for (String className : WEB_ENVIRONMENT_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return false; } } return true; }1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
[/code]
可以看到是根据org.springframework.util.ClassUtils的静态方法去判断classpath里面是否有WEB_ENVIRONMENT_CLASSES包含的类,如果有都包含则返回true则表示启动一个WEB应用,否则返回false启动一个标准Spring的应用。然后通过代码:
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" };1
2
3
1
2
3
[/code]
可以看到是否启动一个WEB应用就是取决于classpath下是否有javax.servlet.Servlet和
org.springframework.web.context.ConfigurableWebApplicationContext。然后进入下一个阶段:
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));1
1
[/code]
这个方法则是初始化classpath下的所有的可用的ApplicationContextInitializer
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));1
1
[/code]
这个方法则是初使化classpath下的所有的可用的ApplicationListener
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; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[/code]
最后找出main方法的全类名并返回其实例并设置到SpringApplication的this.mainApplicationClass完成初始化。然后调用SpringApplication实例的run方法来启动应用,代码如下:
/** * Run the Spring application, creating and refreshing a new * {@link ApplicationContext}. * @param args the application arguments (usually passed from a Java main method) * @return a running {@link ApplicationContext} */ public 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); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); Banner printedBanner = printBanner(environment); context = createApplicationContext(); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); 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); } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
[/code]
由于过程比较长,则把详细说明放入下一篇博客进行说明,这里只做简要的过程说明run()方法的代码中:蓝色部分为加载SpringApplicationRunListener对整个容器的初始化过程进行监听,这里先不做解释,然后先观察剩下的几行的代码:
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); Banner printedBanner = printBanner(environment); context = createApplicationContext(); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments);1
2
3
4
5
6
7
1
2
3
4
5
6
7
[/code]
首先是获取启动时传入参数args并初始化为ApplicationArguments对象
SpringApplication.run(Application.class, args);取这里传入值。
然后配置SpringBoot应用的环境:
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);1
1
[/code]
下面的则打印标志这个方法不说明,因为没有什么实质性作用,反应到控制台则是以下的效果如果确实想玩玩修改一下标志,那可以在项目的classpath下新建一个banner.txt文件,把想打印到控制台的数据放到文件中即可。比如:
src/main/resources/banner.txt中加入以下内容:
_ _ _ _ __ __ _ _ _____ _ _ ___ | | | | | | | | \ \ / / | | | | | ____| | | | | / | | | | | | | | | \ \/ / | | | | | |__ | | | | / /| | | | | | | | | | } { | | | | | __| _ | | | | / / | | | |___ | | | |_| | / /\ \ | |_| | | |___ | |_| | | | / / | | |_____| |_| \_____/ /_/ \_\ \_____/ |_____| \_____/ |_| /_/ |_|1
2
3
4
5
6
1
2
3
4
5
6
[/code]
那么启动的时候就可以看到些标识。
然后下面代码就是比较核心的:
context = createApplicationContext(); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments);1
2
3
4
1
2
3
4
[/code]
首先是createApplicationContext()方法:
/** * Strategy method used to create the {@link ApplicationContext}. By default this * method will respect any explicitly set application context or application context * class before falling back to a suitable default. * @return the application context (not yet refreshed) * @see #setApplicationContextClass(Class) */ protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { contextClass = Class.forName(this.webEnvironment ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS); } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass", ex); } } return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass); }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[/code]
可以看出根据这前初始化过程初始化的this.webEnvironment来决定初始化一个什么容器。如果classpath下是否有javax.servlet.Servlet和
org.springframework.web.context.ConfigurableWebApplicationContext类,
则使用DEFAULT_WEB_CONTEXT_CLASS初始化容器,如果不存在则用DEFAULT_CONTEXT_CLASS初始化容器。
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context." + "annotation.AnnotationConfigApplicationContext"; /** * The class name of application context that will be used by default for web * environments. */ public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework." + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
[/code]
以上是代码指定了容器的类名,最后通过Spring的工具类初始化容器类bean
BeanUtils.instantiate(contextClass);
完成容器的创建工作。然后执行以下的几个步骤完成整个容器的创建与启动以及bean的注入功能。
prepareContext(context, environment, listeners, applicationArguments, printedBanner);1
1
[/code]
以下这一句代码是实现spring-boot-starter-*的自动化配置的关键。包括spring.factories的加载以及自动化配置类的加载以及注入容器的功能可以参考《SpringBoot-自动配置源码解析》
refreshContext(context); afterRefresh(context, applicationArguments);1
2
1
2
[/code]
至此通过SpringBoot启动的容器已经构造完成。这里忽略了启动流程中的收集各种Listener,创建Environment及Environment的初始化的因为这些地方都是SpringBoot提供的各种扩展点,后面的博客会详细的说明各个扩展点的用处以及扩展的方式。
相关文章推荐
- Spring Boot启动流程分析
- 104. Spring Boot 启动流程分析第三篇【从零开始学Spring Boot】
- Spring Boot源码分析之启动流程
- spring boot自动配置与启动流程分析
- u-boot 2011.06 启动流程分析
- u-boot总的启动流程代码分析
- uboot 启动流程分析(二) — 第二阶段
- u-boot启动流程分析
- u-boot-2011.09在ST2410上启动流程分析
- u-boot-2011.06启动流程分析
- uboot启动流程分析和uboot移植(粗略分析)
- spring源码之旅(2)_applicationcontext启动流程分析
- U-boot启动流程(Linux内核)的分析(四)
- U-Boot启动流程(Linux内核)的分析(写得好)
- U-Boot启动流程(Linux内核)的分析(一)
- Atmel SAMA5D3 U-Boot 启动流程简单分析
- u-boot-2011.09在ST2410上启动流程分析
- u-boot-2011.09在ST2410上启动流程分析
- MX51 uboot启动流程分析 - stage2
- U-boot启动流程(Linux内核)的分析(三转)