4. 源码分析-内嵌Tomcat Spring Boot默认支持Tomcat,Jetty,和Undertow作为底层容器。 而Spring Boot默认使用Tomcat,一旦引入spring-boot-starter-web模块,就默认使用Tomcat容器。
<dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency >
4.1 Servlet容器的使用 1. 默认servlet容器 我们看看spring-boot-starter-web这个starter中有什么
核心就是引入了tomcat和SpringMVC
2. 切换servlet容器 如果想切换其他Servlet容器呢,只需如下两步:
将tomcat依赖移除掉
引入其他Servlet容器依赖
引入jetty
<dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > <exclusions > <exclusion > <artifactId > spring-boot-starter-tomcat</artifactId > <groupId > org.springframework.boot</groupId > </exclusion > </exclusions > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jetty</artifactId > </dependency >
4.2 内嵌Tomcat自动配置原理 在启动springboot的时候可谓是相当简单,只需要执行以下代码:
public static void main (String[] args) { SpringApplication.run(SpringBootDemoApplication.class, args); }
那些看似简单的事物,其实并不简单。我们之所以觉得他简单,是因为复杂性都被隐藏了。通过上述代码,大概率可以提出以下几个疑问
SpringBoot是如何启动内置tomcat的 SpringBoot为什么可以响应请求,他是如何配置的SpringMVC 4.3 SpringBoot启动内置tomcat流程 进入SpringBoot启动类,点进@SpringBootApplication源码,如下图:
继续点进@EnableAutoConfiguration,进入该注解,如下图
上图中使用@Import注解对AutoConfigurationImportSelector 类进行了引入,该类做了什么事情呢?进入源码,首先调用selectImport()方法,在该方法中调用了getAutoConfigurationEntry()方法,在之中又调用了getCandidateConfigurations()方法,getCandidateConfigurations()方法就去META-INF/spring.factory配置文件中加载相关配置类
继续打开spring.factories配置文件,找到tomcat所在的类,tomcat加载在ServletWebServerFactoryAutoConfiguration配置类中
进入该类,里面也通过@Import注解将EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow等嵌入式容器类加载进来了,springboot默认是启动嵌入式tomcat容器,如果要改变启动jetty或者undertow容器,需在pom文件中去设置。如下图:
继续进入EmbeddedTomcat类中,见下图:
进入TomcatServletWebServerFactory类,里面的getWebServer()是关键方法,如图:
继续进入getTomcatWebServer()等方法,一直往下跟到tomcat初始化方法,调用tomcat.start()方法,tomcat就正式开启运行,见图
走到这里tomcat在springboot中的配置以及最终启动的流程就走完了,相信大家肯定有一个疑问,上上图中的getWebServer()方法是在哪里调用的呢?上面的代码流程并没有发现getWebServer()被调用的地方。因为getWebServer()方法的调用根本就不在上面的代码流程中,它是在另外一个流程中被调用的。
4.4 getWebServer()的调用分析 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; }
这个方法大概做了以下几件事
获取并启动监听器 通过加载META-INF/spring.factories 完成了 SpringApplicationRunListener实例化工作
构造容器环境,简而言之就是加载系统变量,环境变量,配置文件
创建容器
实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
准备容器
刷新容器
刷新容器后的扩展接口
那么内置tomcat启动源码,就是隐藏在上诉第六步:refreshContext方法里面,该方法最终会调用到AbstractApplicationContext类的refresh()方法进入refreshContext()方法,如下:
@Override public void refresh () throws BeansException, IllegalStateException { synchronized (this .startupShutdownMonitor) { prepareRefresh(); ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); prepareBeanFactory(beanFactory); try { postProcessBeanFactory(beanFactory); invokeBeanFactoryPostProcessors(beanFactory); registerBeanPostProcessors(beanFactory); initMessageSource(); initApplicationEventMulticaster(); onRefresh(); registerListeners(); finishBeanFactoryInitialization(beanFactory); finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } destroyBeans(); cancelRefresh(ex); throw ex; } finally { resetCommonCaches(); } } }
一直点击refresh()方法,如图:onRefresh()会调用到ServletWebServerApplicationContext中的createWebServer()
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(); }
createWebServer()就是启动web服务,但是还没有真正启动Tomcat,既然webServer是通过ServletWebServerFactory来获取的,那就来看看这个工厂的真面目。
可以看到,tomcat,Jetty都实现了这个getWebServer方法,我们看TomcatServletWebServerFactory中的getWebServer(ServletContextInitializer… initializers).
最终就调用了TomcatServletWebServerFactory类的getWebServer()方法。
4.5 小结 springboot的内部通过new Tomcat() 的方式启动了一个内置Tomcat。但是这里还有一个问题, 这里只是启动了tomcat,但是我们的springmvc是如何加载的呢?