SpringBoot如何使用内嵌Tomcat问题
作者:吉星23526
前言
Springboot使用了内置tomcat,在服务部署时无需额外安装Tomcat,直接启动jar包即可。
这里会有很多疑问
- 1 内置Tomcat长什么样,它与原来的Tomcat有啥区别
- 2 Springboot是如何使用的内置tomcat
- 3 DispatcherServlet是如何加载到tomcat容器的
对于以上的种种疑问在这里做了一个总结
一、原来的Tomcat启动流程
1 运行catalina.sh start脚本 最终会执行Bootstrap的mian方法
eval exec "\"$_RUNJDB\"" "\"$CATALINA_LOGGING_CONFIG\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \ -D$ENDORSED_PROP="$JAVA_ENDORSED_DIRS" \ -classpath "$CLASSPATH" \ -sourcepath "$CATALINA_HOME"/../../java \ -Dcatalina.base="$CATALINA_BASE" \ -Dcatalina.home="$CATALINA_HOME" \ -Djava.io.tmpdir="$CATALINA_TMPDIR" \ //这里会运行Bootstrap的main方法 并传入start参数 org.apache.catalina.startup.Bootstrap "$@" start fi
2 执行Bootstrap的mian方法 构建Catalina对象 并执行其load和start方法
//全局变量 用于保存Bootstrap实例 private static volatile Bootstrap daemon = null; //全局变量 用于保存Catalina对象 private Object catalinaDaemon = null; public static void main(String args[]) { synchronized (daemonLock) { if (daemon == null) { // Don't set daemon until init() has completed Bootstrap bootstrap = new Bootstrap(); try { //这里 构建Catalina对象并赋值给全局变量catalinaDaemon bootstrap.init(); } catch (Throwable t) { ... } //这里初始化了全局变量 daemon = bootstrap; } else { ... } try { ... if (command.equals("start")) { daemon.setAwait(true); //本质是调用了Catalina对象的load方法 daemon.load(args); //本质上是调用了Catalina的start方法 daemon.start(); ... } } catch (Throwable t) { ... } ... } //构建Catalina对象并赋值给全局变量catalinaDaemon public void init() throws Exception { ... //通过反射构建Catalina对象 Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina"); Object startupInstance = startupClass.getConstructor().newInstance(); ... //这里把Catalina对象赋予了全局变量catalinaDaemon catalinaDaemon = startupInstance; } //本质是调用了Catalina对象的load方法 private void load(String[] arguments) throws Exception { // Call the load() method String methodName = "load"; ... Method method = catalinaDaemon.getClass().getMethod(methodName, paramTypes); //这里就是调用了Catalina对象的load方法 method.invoke(catalinaDaemon, param); } //本质上是调用了Catalina的start方法 public void start() throws Exception { Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null); method.invoke(catalinaDaemon, (Object [])null); }
3 Catalina的load方法
//全局变量Server对象 该对象通过解析server.xml生成 protected Server server = null; public void load() { ... // Parse main server.xml // 解析server.xml文件 初始化server对象 parseServerXml(true); Server s = getServer() ... // Start the new server try { getServer().init(); } catch (LifecycleException e) { ... } ... }
server.xml的结构是一个4层嵌套的树状结构。一层也就是根节点是server元素,二层是service元素,三层是Engine元素,四层是Host元素。
最终其被解析Server对象。该对象内部包含一组service对象,每个service对象包含一个Engine对象,每个Engine对象包含一组Host对象。
其实每个Host对象还对应一组Context对象也就是我们常说的Servlet容器,只是在server.xml文件中体现的比较隐晦。Host对象有一个属性叫做appBase,该属性的默认值是webapps,最终解析时会去Tomcat根目录下的webapps文件中找web.xml,找到一个就生成一个Context对象
4 Catalina的start方法
//本质上就是调用server的start方法 public void start() { ... // Start the new server try { getServer().start(); } catch (LifecycleException e) { ... } ... } //返回全局变量server public Server getServer() { return server; }
这里蕴含这一个设计模式值得一提,通过load方法可以知道Server内部有一组service,每个service内部有一个Engine,每个Engine内部有一组host,每个host内部有一组context。
这里提到的每一个对象都有init方法和start方法,在server的start方法被调用后需要执行其下每个service对象的init方法和start方法,当service的start方法被调用后需要执行其下Engine的init方法和start方法以此类推一直到调用完Context的init方法和start方法。
Tomcat使用抽象模板的设计模式完成了该流程的实现。
首先看看抽象模板类LifecycleBase,上述提到的所有对象都继承该类,该类有4个主要方法,其中start是模板类的核心方法
public abstract class LifecycleBase implements Lifecycle { //抽象模板类提供的公共方法 public final synchronized void init() throws LifecycleException { if (!state.equals(LifecycleState.NEW)) { invalidTransition(Lifecycle.BEFORE_INIT_EVENT); } try { setStateInternal(LifecycleState.INITIALIZING, null, false); //该方法是一个抽象方法由子类完成实现 //server类的实现方式 就是便利其内部的sercie对象 挨个调用其init方法 //service类的实现方法 就是调用engine的 init方法 //engine的实现方法 就是便利其内部的host对象 挨个调用其init方法 //以此类推。。。 initInternal(); //这里会发生状态变更 防止重复init用的 setStateInternal(LifecycleState.INITIALIZED, null, false); } catch (Throwable t) { handleSubClassException(t, "lifecycleBase.initFail", toString()); } } //抽象模板类提供的公共方法 public final synchronized void start() throws LifecycleException { if (state.equals(LifecycleState.NEW)) { //start方法中会先执行init方法 init(); } else if (state.equals(LifecycleState.FAILED)) { ... } else if (!state.equals(LifecycleState.INITIALIZED) && ... } ... try { setStateInternal(LifecycleState.STARTING_PREP, null, false); //该方法是一个抽象方法由子类完成实现 //server类的实现方式 就是便利其内部的sercie对象 挨个调用其start方法 //service类的实现方法 就是调用engine的 start方法 //engine的实现方法 就是便利其内部的host对象 挨个调用其start方法 //以此类推。。。 startInternal(); ... } catch (Throwable t) { ... } } //子类实现 protected abstract void initInternal() throws LifecycleException; //子类实现 protected abstract void startInternal() throws LifecycleException; }
基于对LifecycleBase的4个方法的分析,我们看看当server的start方法被调用时会发生什么
1 server的start方法会调用其父类LifecycleBase的公共start方法
2 接着会调用LifecycleBase的init方法
3 接着会调用LifecycleBase的initInternal方法,该方法由子类server实现,便利其下的service对象挨个调用init方法
4 service对象的init方法是由父类LifecycleBase实现的,所以会执行LifecycleBase的init方法。这里有一个状态变更即元素的state状态由LifecycleState.NEW变成了LifecycleState.INITIALIZING防止在start方法中再次执行init方法
5 以此类推最终所有元素的init方法会被调用并且状态变成了LifecycleState.INITIALIZING,最终又回到了server的start方法此时init方法已经执行完了
6继续向下走执行startInternal方法,该方法由子类server实现,便利其下的service对象挨个调用start方法
7start方法由父类LifecycleBase实现的,所以会执行LifecycleBase的start方法,此时因为对象状态已经不是new状态了,init方法不会执行,继续执行startInternal方法,以此类推最终所有元素的start方法会被执行
最终各个元素的init和start方法都被执行了一遍
二、内嵌Tomcat
阿帕奇提供了一个类,名字就叫Tomcat。该类和Catalina类十分相似,内部也有一个Server对象并且提供了start方法,本质也是调用的server.start。
接下来看看这个类
public class Tomcat { //全局变量 protected Server server; //启动方法 public void start() throws LifecycleException { getServer(); //本质是server的start方法 server.start(); } //重点在后边的这几个方法 //获取server public Server getServer() { ... if (server != null) { return server; } //这里直接new对象了 不像Catalina那样需要解析server.xml文件 server = new StandardServer(); initBaseDir(); ... //顺便为其创建了一个service对象 Service service = new StandardService(); service.setName("Tomcat"); server.addService(service); return server; } //获取service 内部调用了getServer 一样的道理 没有就new public Service getService() { return getServer().findServices()[0]; } //获取引擎 一样的逻辑 没有就new public Engine getEngine() { Service service = getServer().findServices()[0]; if (service.getContainer() != null) { return service.getContainer(); } Engine engine = new StandardEngine(); engine.setName( "Tomcat" ); engine.setDefaultHost(hostname); engine.setRealm(createDefaultRealm()); service.setContainer(engine); return engine; } //获取host 同上没有就new public Host getHost() { Engine engine = getEngine(); if (engine.findChildren().length > 0) { return (Host) engine.findChildren()[0]; } Host host = new StandardHost(); host.setName(hostname); getEngine().addChild(host); return host; } }
最终可以发现内嵌Tomcat本质上和Catalina对象一样,都是通过初始化一个Server对象然后调用Server对象的start方法完成tomcat启动的。
区别就是初始化Server的过程不在需要解析server.xml文件了,各种get就能完成初始化。
三、Springboot启动Tomcat的时机
springboot启动类的mian方法中会执行SpringApplication.run方法,该方法会创建并启动一个容器[AnnotationConfigServletWebServerApplicationContext],容器启动会执行祖先类AbstractApplicationContext的refresh方法,该方法中的onRefresh方法被AnnotationConfigServletWebServerApplicationContext的父类ServletWebServerApplicationContext重写了,内置Tomcat就在onRefresh方法中被启动了
接下来看下ServletWebServerApplicationContext的onRefresh方法
public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext { //重写了父类的方法 @Override protected void onRefresh() { super.onRefresh(); try { //重点在这里 createWebServer(); } .... } //创建WebServer,其内部封装了Tomcat对象 private void createWebServer() { WebServer webServer = this.webServer; //这里取Tomcat容器对象 首次是取不到的 ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { //获取工厂对象 该对象是自动装配中装配的 默认是TomcatServletWebServerFactory ServletWebServerFactory factory = getWebServerFactory(); //通过工厂创建WebServer对象,WebServer对象创建完Tomcat就会启动 所以重点跟下这里 this.webServer = factory.getWebServer(getSelfInitializer()); getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer)); getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer)); } else if (servletContext != null) { ... } initPropertySources(); } }
最终会通过TomcatServletWebServerFactory工厂类构建WebServer对象,跟getWebServer方法
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware { //主要方法 @Override public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.disableMBeanRegistry) { Registry.disableRegistry(); } //内嵌Tomcat对象 Tomcat tomcat = new Tomcat(); File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); connector.setThrowOnFailure(true); //这里可以知道 其创建了server对象和service对象 并为service对象设置了connector tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); //这里可以知道 其创建了engine对象和host对象 tomcat.getHost().setAutoDeploy(false); configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } //这里创建了Servlet容器 prepareContext(tomcat.getHost(), initializers); return getTomcatWebServer(tomcat); } //创建Servlet容器 protected void prepareContext(Host host, ServletContextInitializer[] initializers) { File documentRoot = getValidDocumentRoot(); //直接new了一个容器 该类是StandardContext的子类 TomcatEmbeddedContext context = new TomcatEmbeddedContext(); if (documentRoot != null) { context.setResources(new LoaderHidingResourceRoot(context)); } ... ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); //将容器放入host对象中 host.addChild(context); configureContext(context, initializersToUse); postProcessContext(context); } //构造TomcatWebServer对象将Tomcat对象封装其中 protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown()); } }
工厂类中会创建Tomcat对象,并初始化其内部的Server对象。
最终将Tomcat对象封装到TomcatWebServer中返回,接着看下TomcatWebServer的构造器
public class TomcatWebServer implements WebServer { //用于封装Tomcat对象 private final Tomcat tomcat; public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) { Assert.notNull(tomcat, "Tomcat Server must not be null"); //初始化Tomcat对象 this.tomcat = tomcat; this.autoStart = autoStart; this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null; //重点看这里 这里启动了Tomcat initialize(); } //启动了Tomcat private void initialize() throws WebServerException { logger.info("Tomcat initialized with port(s): " + getPortsDescription(false)); synchronized (this.monitor) { try { ... // Start the server to trigger initialization listeners this.tomcat.start(); ... } catch (Exception ex) { ... } } } }
到这里可以知道工厂类在构造WebServer之后,Tomcat就被启动了,这里就是内嵌Tomcat的启动时机。
和原来相比,原来的启动类是Tomcat,再由Tomcat启动触发容器的创建和启动,而现在的启动类是容器,由容器启动触发了Tomcat的启动
四、SpringBoot中的Tomcat如何加载Servlet
4.1 Servlet3.0标准可以不使用web.xml完成Servlet的注册
早期的项目一个web.xml文件最终被解析成一个Context对象【容器对象】,web.xml内部可以配置很多servlet,最终在解析完web.xml会将解析出来的servlet对象注册到容器中。
而springboot项目中并没有web.xml文件,所以引发了一个问题。
Servlet对象是如何被注册到Tomcat容器中的呢?
servlet3.0标准中提供了一个不用web.xml也能加载Servlet的方法。
需要三步:
- 1 写一个类实现ServletContainerInitializer接口
- 2 实现ServletContainerInitializer接口的onStartup方法
- 3 在/META-INF/services目录下创建javax.servlet.ServletContainerInitializer文件,将实现类的全名称写入到配置文件中
实现完以上步骤,Tomcat启动后会回调实现类的onStartup方法,并将Servlet容器的装饰类【ServletContext】当作入参传入onStartup方法。
看下ServletContext这个类的方法
public interface ServletContext { public ServletRegistration.Dynamic addServlet(String servletName, String className); public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet); public ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass); }
这个类有很多方法,其中新增servlet的就有3个重载方法。
也就是说我们写的实现类在实现onStartup的方法中就可以调用ServletContext的addServlet方法完成Servlet的注册了。
4.2 SpringBoot中的ServletContainerInitializer的实现类
那么SpringBoot中的Tomcat就是用这个方式加载的Servlet吗?是也不全是。
springboot确实搞了一个实现类TomcatStarter来实现ServletContainerInitializer接口并实现了onStartup方法。
但是和web.xml文件一样javax.servlet.ServletContainerInitializer文件在springboot项目中也没有。
其实与写javax.servlet.ServletContainerInitializer文件的方式相比还有一种更加简单粗暴的方式,在Context对象创建好后直接调用其addServletContainerInitializer方法将ServletContainerInitializer的实现类传进去。
再次看下创建Context对象的地方
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware { //创建Servlet容器 protected void prepareContext(Host host, ServletContextInitializer[] initializers) { File documentRoot = getValidDocumentRoot(); //直接new了一个容器 该类是StandardContext的子类 TomcatEmbeddedContext context = new TomcatEmbeddedContext(); if (documentRoot != null) { context.setResources(new LoaderHidingResourceRoot(context)); } ... ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); //将容器放入host对象中 host.addChild(context); //这个方法之前没根 这次下这个方法 configureContext(context, initializersToUse); postProcessContext(context); } protected void configureContext(Context context, ServletContextInitializer[] initializers) { //创建了ServletContainerInitializer的实现类 TomcatStarter starter = new TomcatStarter(initializers); ... //这里直接将其放入到了容器中 context.addServletContainerInitializer(starter, NO_CLASSES); ... } }
4.3 ServletContainerInitializer的实现类执行onStartup方法的时机
之前分析过server.start方法执行后各个元素的init、start、initInternal、startInternal都会被调用,Context对象也不例外。
接着看下Context的startInternal方法。
虽然我们的Context对象类型是TomcatEmbeddedContext,但是startInternal方法是由其父类StandardContext实现的。
所以看下StandardContext类
public class StandardContext extends ContainerBase implements Context, NotificationEmitter { //内部有一个集合 用于保存所有ServletContainerInitializer的实现类 private Map<ServletContainerInitializer,Set<Class<?>>> initializers = new LinkedHashMap<>(); //还记得这个方法吗TomcatEmbeddedContext就是通过该方法将TomcatStarter添加进来的 public void addServletContainerInitializer( ServletContainerInitializer sci, Set<Class<?>> classes) { initializers.put(sci, classes); } //Tomcat启动时会执行该方法 这个方法巨长无比 我只把关键的保留了 protected synchronized void startInternal() throws LifecycleException { //便利集合 for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializers.entrySet()) { try { //集合中的key就是ServletContainerInitializer的实现类 这里调用了onStartup方法 entry.getKey().onStartup(entry.getValue(), //最后看下getServletContext方法,看看容器的装饰类到底是什么 getServletContext()); } catch (ServletException e) { log.error(sm.getString("standardContext.sciFail"), e); ok = false; break; } } } //这里可以知道容器最终把自己封装到了ApplicationContext对象中, //最终将ApplicationContext对象暴露给ServletContainerInitializer实现类 public ServletContext getServletContext() { if (context == null) { context = new ApplicationContext(this); if (altDDName != null) context.setAttribute(Globals.ALT_DD_ATTR,altDDName); } return context.getFacade(); } }
也就是容器对象启动后,在执行其startInternal方法是会调用ServletContainerInitializer的实现类的onStartup方法并将容器对象的装饰类ApplicationContext当作入参传入onStartup方法。
4.4 分析TomcatStarter的onStartup方法
铺垫了那么多,我们看下TomcatStarter的onStartup方法
class TomcatStarter implements ServletContainerInitializer { //一堆ServletContextInitializer接口的实现类 private final ServletContextInitializer[] initializers; //构造器 初始化内部的initializers属性 TomcatStarter(ServletContextInitializer[] initializers) { this.initializers = initializers; } //这个方法里没有任何servlet的添加操作,而是便利了initializers,并执行initializers每一个实例的onStartup方法,将servletContext当入参传入其中 @Override public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException { try { for (ServletContextInitializer initializer : this.initializers) { initializer.onStartup(servletContext); } } catch (Exception ex) { .... } } }
和想象中的不一样,onStartup方法中并没有添加servlet,而是将ServletContext对象再次传给了ServletContextInitializer的实现类去完成后续工作。
为什么要这样做呢?
其实原因很简单,到目前为止要想拿到ServletContext对象就必须实现ServletContainerInitializer接口。
而ServletContainerInitializer接口并不是spring的类。
所以spring搞了一个自己的接口ServletContextInitializer并且内部也有一个待实现的方法onStartup。
spring想实现的目标是所有实现了ServletContextInitializer接口的bean都能拿到ServletContext对象。
最终借助TomcatStarter类中的onStartup完成了实现。
大致看下实现过程,起点在ServletWebServerApplicationContext类中
public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext { //最终这个私有方法会被调用,可以看出如果TomcatStarter中的onStartup方法能调用到该方法,上述说的spirng目的就达成了 private void selfInitialize(ServletContext servletContext) throws ServletException { prepareWebApplicationContext(servletContext); registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext); //这里拿到容器中所有实现了ServletContextInitializer接口的bean并依次执行其onStartup方法 入参是servletContext for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); } } //ServletContextInitializer本身是一个@FunctionalInterface //这里将上述的私有方法封装成了一个ServletContextInitializer实例 很感慨既然还能这样干 private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { return this::selfInitialize; } //还记得这个方法吗,这里通过factory完成了WebServer的创建,也就是tomcat启动的位置 private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { ServletWebServerFactory factory = getWebServerFactory(); //这里将ServletContextInitializer实例传入到了TomcatServletWebServerFactory中 this.webServer = factory.getWebServer(getSelfInitializer()); ... } .... } }
上述可以看到spring用一种很诡异的方式将一个私有方法封装成了ServletContextInitializer实例并传给了TomcatServletWebServerFactory的getWebServer方法中,再次根下TomcatServletWebServerFactory类。
这次主要看ServletContextInitializer实例的传递过程
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware { //这里ServletContextInitializer实例被传入 public WebServer getWebServer(ServletContextInitializer... initializers) { ... //被传入到该方法 prepareContext(tomcat.getHost(), initializers); return getTomcatWebServer(tomcat); } protected void prepareContext(Host host, ServletContextInitializer[] initializers) { //这里做了依次封装 之前的ServletContextInitializer实例就在其中 ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); //根这里 configureContext(context, initializersToUse); postProcessContext(context); } //做了一层扩展 protected final ServletContextInitializer[] mergeInitializers(ServletContextInitializer... initializers) { List<ServletContextInitializer> mergedInitializers = new ArrayList<>(); mergedInitializers.add((servletContext) -> this.initParameters.forEach(servletContext::setInitParameter)); mergedInitializers.add(new SessionConfiguringInitializer(this.session)); //被传入到mergedInitializers集合中 mergedInitializers.addAll(Arrays.asList(initializers)); mergedInitializers.addAll(this.initializers); //集合转数组 return mergedInitializers.toArray(new ServletContextInitializer[0]); } //最终会将ServletContextInitializer传入TomcatStarter的构造函数,和之前说的完全对应上了 protected void configureContext(Context context, ServletContextInitializer[] initializers) { TomcatStarter starter = new TomcatStarter(initializers); ... } }
通过对TomcatStarter的onStartup方法的分析可以知道,所有实现了ServletContextInitializer接口的bean都能拿到ServletContext对象,完成servlet对象的添加
4.5 DispatcherServlet如何加载到Tomcat容器
springboot会自动装配springmvc,而springmvc的核心类就是DispatcherServlet。
上边铺垫了那么多最终看看DispatcherServlet是如何加载到tomcat中的
首先看下自动装配类DispatcherServletAutoConfiguration
...省略注解 public class DispatcherServletAutoConfiguration { ...省略注解 protected static class DispatcherServletConfiguration { //这里创建了DispatcherServlet类 @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) { ...省略DispatcherServlet构造内容 return dispatcherServlet; } ... ...省略注解 protected static class DispatcherServletRegistrationConfiguration { ...省略注解 //重点是这个类,上边的DispatcherServlet会被传入到该类中,最终由该类完成DispatcherServlet向Tomcat容器的注册 public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) { DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath()); registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup()); multipartConfig.ifAvailable(registration::setMultipartConfig); return registration; } } } }
可以看到自动装配时向spring容器中注册了DispatcherServletRegistrationBean类,该类构造器中包含DispatcherServlet对象。
看下DispatcherServletRegistrationBean类的家谱
可以看到该类实现了ServletContextInitializer接口也就是其能拿到Tomcat容器对象。
看下其祖先类RegistrationBean的onStartup方法
public abstract class RegistrationBean implements ServletContextInitializer, Ordered { public final void onStartup(ServletContext servletContext) throws ServletException { String description = getDescription(); if (!isEnabled()) { logger.info(StringUtils.capitalize(description) + " was not registered (disabled)"); return; } //根这个方法 该方法由子类DynamicRegistrationBean实现 register(description, servletContext); } }
DynamicRegistrationBean类
public abstract class DynamicRegistrationBean<D extends Registration.Dynamic> extends RegistrationBean { protected final void register(String description, ServletContext servletContext) { //servlet注册在这里完成 该方法由子类ServletRegistrationBean实现 //servlet注册完后会返回一个registration对象,用于完成servlet-mapping的配置 D registration = addRegistration(description, servletContext); if (registration == null) { logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)"); return; } //servlet的mapping配置在这里完成 该方法由子类ServletRegistrationBean实现 configure(registration); } }
ServletRegistrationBean类
public class ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<ServletRegistration.Dynamic> { //这里可以看到servletContext.addServlet方法终于被调用了 @Override protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) { String name = getServletName(); //this.servlet就是DispatcherServlet return servletContext.addServlet(name, this.servlet); } //这里来配置servlet-mapping @Override protected void configure(ServletRegistration.Dynamic registration) { super.configure(registration); String[] urlMapping = StringUtils.toStringArray(this.urlMappings); if (urlMapping.length == 0 && this.alwaysMapUrl) { urlMapping = DEFAULT_MAPPINGS; } if (!ObjectUtils.isEmpty(urlMapping)) { registration.addMapping(urlMapping); } registration.setLoadOnStartup(this.loadOnStartup); if (this.multipartConfig != null) { registration.setMultipartConfig(this.multipartConfig); } } }
总结
1 springboot使用内嵌Tomcat完成了tomcat的启动。内嵌Tomcat本质上和正常Tomcat的Catalina对象一样都是通过初始化内部的server对象,最终调用server对象的start方法来完成启动的。区别就是server对象的创建构成,前者直接new后者解析server.xml文件
2 springboot中tomcat的启动时机是在容器启动时,执行onRefresh方法中。创建webServer对象时启动的。
3 springboot基于servlet3.0标准。创建了ServletContainerInitializer的实现类TomcatStarter最终拿到Tomcat容器对象
4 springboot基于TomcatStarter拿到的tomcat容器对象做了进一步优化。最终实现了所有实现ServletContextInitializer接口的bean都能拿到tomcat容器
5 ServletContextInitializer的实现类之一DispatcherServletRegistrationBean完成了DispatcherServlet向tomcat容器的注册
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。