Springboot版本是2.0.5.release.
如下List-1所示是我们平时使用Springboot的方式,底层上发生了些什么呢,我们接下来分析下。
List-1
@SpringBootApplication
public class HelloApplication {
public static void main(String[] args) {
SpringApplication.run(HelloApplication.class, args);
}
}
SpringApplication的静态方法run,最终会先构造SpringApplication之后调用run方法,如下List-2,primarySources是我们传入的HelloApplication,args数组是List-1中传入的args。
List-2
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
1、SpringApplication的构造方法
来看SpringApplication的构造方法,如下List-3,
List-3
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();//1
setInitializers((Collection) getSpringFactoriesInstances( //2
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//3
this.mainApplicationClass = deduceMainApplicationClass();//4
}
- 判断是servlet web、reactive web还是普通的应用,如List-4所示,根据classpath是否有对应的类判断是哪种类型,当我们引入spring web依赖和servlet依赖,则是servlet web应用。
- 从spring.factories中获得所有ApplicationContextInitializer对应的值,之后实例化并进行排序,之后全部添加到SpringApplication的属性initializers中。
- 从spring.factories中获得所有ApplicationListener的值,之后实例化并进行排序,之后全部添加到SpringApplication的属性listeners中。
- 有意的抛出一个异常,之后判断哪个含有main方法,即List-1中的HelloApplication。
List-4
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 JERSEY_WEB_ENVIRONMENT_CLASS = "org.glassfish.jersey.server.ResourceConfig";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
2、SpringApplication的run方法
如下List-5,内容看着有些多,我们逐个深入
List-5
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);//1
listeners.starting();//2
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);//3
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);//4
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);//5
context = createApplicationContext();//6
exceptionReporters = getSpringFactoriesInstances(//7
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,//8
printedBanner);
refreshContext(context);//9
afterRefresh(context, applicationArguments);//10
stopWatch.stop();//11
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)//12
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);//13
callRunners(context, applicationArguments);//14
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);//15
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
- 从spring.factories中获取SpringApplicationRunListener的值,实例化、排序,之后放到SpringApplicationRunListeners中,SpringApplicationRunListeners使用了组合设计模式。
- 调用starting方法。
- 构造DefaultApplicationArguments实例。
- 获取environment,4里面有很多操作,如List-6所示,getOrCreateEnvironment()方法根据类型创建对应的environment,我们一般是servlet web应用,则创建StandardServletEnvironment;"configureEnvironment(environment, applicationArguments.getSourceArgs())"将List-1中传到main函数的参数数组解析到environment;"bindToSpringApplication(environment)"将environment绑定到SpringApplication中。
- 打印banner,如List-7所示,bannerMode默认是Banner.Mode.CONSOLE,即打印到控制台,2位置处打印banner,默认使用的是SpringBootBanner,如List-8所示,List-8中的printBanner方法的参数printStream默认是System.out。
- 会根据应用的类型返回对应的applicationContext,以servlet web为例,创建的是AnnotationConfigServletWebServerApplicationContext,具体较为简单,可以看源码。
- 从spring.factories中获取SpringBootExceptionReporter的值,实例化排序。
- 该方法里面没有太多的操作,会逐个调用SpringApplication的ApplicationContextInitializer.initialize()方法,之后会将environment和printBanner注册到Spring的beanFactory里面。
- 其实是调用AbstractApplicationContext的refresh()方法,AbstractApplicationContext的refresh方法里面有很多内容,这里不详细讲。Springboot启动tomcat就是和这里有关了。
- 这个方法为空。
- StopWatch的stop方法,记录启动springboot用时多少。
- 将Springboot启动用时多少时间打印出来,这个就是我们平时在控制台看到类似"15:57:28.657 INFO Started HelloApplication in 4.643 seconds (JVM running for 5.18)"的代码,如List-9所示。
- 调用listeners的started方法。
- 如List-10,从BeanFactory中获取ApplicationRunner和CommandLineRunner,之后排序,再逐个调用run方法。
List-6
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
List-7
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null)
? this.resourceLoader : new DefaultResourceLoader(getClassLoader());
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(//1
resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);//2
}
List-8
class SpringBootBanner implements Banner {
private static final String[] BANNER = { "",
" . ____ _ __ _ _",
" /\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\",
"( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",
" \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )",
" ' |____| .__|_| |_|_| |_\\__, | / / / /",
" =========|_|==============|___/=/_/_/_/" };
private static final String SPRING_BOOT = " :: Spring Boot :: ";
private static final int STRAP_LINE_SIZE = 42;
@Override
public void printBanner(Environment environment, Class<?> sourceClass,
PrintStream printStream) {
for (String line : BANNER) {
printStream.println(line);
}
String version = SpringBootVersion.getVersion();
version = (version != null) ? " (v" + version + ")" : "";
StringBuilder padding = new StringBuilder();
while (padding.length() < STRAP_LINE_SIZE
- (version.length() + SPRING_BOOT.length())) {
padding.append(" ");
}
printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT,
AnsiColor.DEFAULT, padding.toString(), AnsiStyle.FAINT, version));
printStream.println();
}
}
List-9
private StringBuilder getStartedMessage(StopWatch stopWatch) {
StringBuilder message = new StringBuilder();
message.append("Started ");
message.append(getApplicationName());
message.append(" in ");
message.append(stopWatch.getTotalTimeSeconds());
try {
double uptime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000.0;
message.append(" seconds (JVM running for " + uptime + ")");
}
catch (Throwable ex) {
// No JVM time available
}
return message;
}
List-10
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
3、Springboot是如何启动内嵌的tomcat的
看List-5的9里面,调用的是AbstractApplicationContext.refresh->AbstractApplicationContext->onRefresh()->ServletWebServerApplicationContext.onRefresh(),AnnotationConfigServletWebServerApplicationContext的父类是ServletWebServerApplicationContext,来看下ServletWebServerApplicationContext的onRefresh方法,如List-11所示,createWebServer()里面,创建web服务容器,有可能是tomcat/jetty等。这样就创建好了web容器,之后refresh完成后就启动web容器了。
List-11
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
思考: 我们可以修改为使用jetty容器而不是tomcat这是怎么做到的? 看List-11中getWebServerFactory(),从beanFactory中获取ServletWebServerFactory类型的bean,之后用该工厂创建webServer。WebServerFactory的实现类很多,具体使用哪个:
ServletWebServerFactoryConfiguration如下List-2,根据ConditionalOnClass而后注入不同的WebServerFactory实现,其实还有个ReactiveWebServerFactoryConfiguration,这里面的是reactive的。
List-2
@Configuration
class ServletWebServerFactoryConfiguration {
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
}
/**
* Nested configuration if Jetty is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
WebAppContext.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
@Bean
public JettyServletWebServerFactory JettyServletWebServerFactory() {
return new JettyServletWebServerFactory();
}
}
/**
* Nested configuration if Undertow is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow {
@Bean
public UndertowServletWebServerFactory undertowServletWebServerFactory() {
return new UndertowServletWebServerFactory();
}
}
}