对SpringBoot源码的一些浅析。
SpringBoot的启动方式很简单,本文也是从以下代码入手
MyApplication.java1 2 3 4 5 6
| @SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
|
一、SpringBoot启动源码分析
本文介绍的SpringBoot版本是2.0.4,各版本的源码可能略有不同,但思路几乎是一致的。
SpringBoot的启动主要有两块代码,SpringApplication类的实例化和run()方法,这里只简述两个方法的主要内容。
SpringApplication构造器方法:
1、从classLoader中拿所有spring.factories文件,读取ApplicationContextInitializer配置
2、从classLoader中拿所有spring.factories文件,读取ApplicationListener配置
run()方法:
1、创建ApplicationContext容器,容器类型为AnnotationConfigServletWebServerApplicationContext
2、调用所有ApplicationContextInitializer
3、调用ApplicationContext的refresh()方法,完成对Bean的读取、解析、注册、实例化,以及tomcat容器的启动等(这步其实完全是ApplicationContext的东西)
总结:
从SpringBoot启动的源码上来看,SpringBoot主要是在ApplicationContext的基础上实现了spring.factories文件的支持,其他的东西都是ApplicationContext完成的,所以SpringBoot比较像ApplicationContext的“一层壳”。
SpringApplication.java1 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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| 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)); this.mainApplicationClass = deduceMainApplicationClass(); }
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); 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); 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; }
|
二、SpringBoot常见问题
- SpringBoot启动了几个容器,怎么包装DispatcherServlet、ApplicationContext的?
1个,类型为AnnotationConfigServletWebServerApplicationContext
SpringMVC在web.xml中配置ContextLoaderListener启动一个根容器XmlWebApplicationContext,DispatcherServlet又启动了一个子容器XmlWebApplicationContext,以此来实现了Spring、SpringMVC父子容器的功能。
SpringBoot会启动AnnotationConfigServletWebServerApplicationContext作为根容器,DispatcherServlet也会初始化,但这时并不会再启动一个子容器,而是复用根容器,所以也不再有SpringMVC中父子容器的功能。
- SpringBoot在哪初始化DispatcherServlet的?
在DispatcherServletAutoConfiguration类中有一个@Bean注解,SpringBoot扫描该注解初始化了DispatcherServlet。该类所在包名spring-boot-autoconfigure,来自于spring-boot-starter,是SpringBoot提供的开箱即用的包。
DispatcherServletAutoConfiguration.java1 2 3 4 5 6 7 8
| @Bean(name = {"dispatcherServlet"}) public DispatcherServlet dispatcherServlet() { DispatcherServlet dispatcherServlet = new DispatcherServlet(); dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest()); dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest()); dispatcherServlet.setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound()); return dispatcherServlet; }
|
- SpringBoot在哪启动Tomcat容器的,又怎么hold住线程的?
AnnotationConfigEmbeddedWebApplicationContext容器refresh的时候,会调用onRefresh方法。而AnnotationConfigServletWebServerApplicationContext的onRefresh方法会启动嵌入式的服务容器,比如tomcat或jetty。
有趣的是,SpringMVC是在容器初始化时就会初始化九大组件,而SpringBoot是在第一次请求过来时才会初始化九大组件,可以在DispatcherServlet#onRefresh方法打个断点验证此结论。
- SpringBoot、ApplicationContext、BeanFactory仅仅是一层一层的包装而已吗?
个人从宽松的概念看,还真是这样。
BeanFactory是最底层结构,实现了对Bean的注册、实例化等容器管理功能。
ApplicationContext是BeanFactory的上层抽象,在BeanFactory基础上做了一些BeanPostProcessor和BeanFactoryPostProcessor的注册等初始配置,BeanPostProcessor和BeanFactoryPostProcessor是两者之间的纽带,具体调用是BeanFactory去完成的。
SpringBoot又是对ApplicationContext的一层包装,提供了开箱即用的功能和对spring.factories文件的支持,而实际干活的都是ApplicationContext。
SpringBoot只是对ApplicationContext的一层包装,扫描注册Bean肯定是ApplicationContext做的咯,看ApplicationContext吧。
项目中引入spring-boot-starter-parent,而spring-boot-starter-parent中又导入了spring-boot-dependencies作为parent。spring-boot-dependencies中使用了标签来定义了很多依赖的版本(并未实际引入),所以子pom.xml只需要在标签中引入一个依赖,都会继承父pom的版本,都不用显式定义版本,这样就完成了对版本的统一管理。
pom.xml1 2 3 4 5 6 7 8 9 10 11 12
| <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
|
spring-boot-starter-parent-2.0.4.RELEASE.pom1 2 3 4 5 6
| <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.0.4.RELEASE</version> <relativePath>../../spring-boot-dependencies</relativePath> </parent>
|
spring-boot-starter-parent-2.0.4.RELEASE.pom1 2 3 4 5 6 7 8 9 10
| <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.0.4.RELEASE</version> </dependency> ...... </dependencies> </dependencyManagement>
|
参考
https://github.com/fangjian0423/springboot-analysis
http://fangjian0423.github.io/categories/springboot/