概述
- SpringBoot使用main方法启动的一个重要特性是,不需要打包成war部署到Tomcat这种Servlet容器中,而是只需打包成jar,然后通过java或mvn等命令运行这个jar包,然后应用就可以在指定的端口监听客户端的连接请求了。
- 在SpringBoot内部主要是引用了embedded的Tomcat或Jetty等作为Servlet引擎,由该Servlet引擎负责接收Web请求并交给应用处理和生成响应,从而可以将应用打包成jar,直接在命令行启动,以独立进程的方式运行,无需依赖Tomcat等Servlet容器,但是可以实现跟部署到Tomcat中的war包一样处理Web请求和响应。
接口设计
- 在接口设计层面,对Spring容器而言,是通过拓展ApplicationContext接口来增加Spring容器对Servlet引擎的支持;对Servlet引擎而言,则是定义了WebServer接口来代表Servlet引擎或者说是Web服务器,WebServer接口实现类为具体的Servlet引擎实现。
-
Spring容器通过包含WebServer的引用来负责Servlet引擎的启动关闭。同时与普通Servlet容器实现(如Tomcat)一样,将Spring应用通过关联一个ServletContext引用来建立与Servlet引擎的关联,同时将自身保存为ServletContext引用的attribute。
-
应用自身在启动的时候,会创建和启动Spring容器,在Spring容器中通过该WebServer引用来启动对应的Servlet引擎。并在启动Servlet引擎过程中,创建该应用对应的ServletContext,由该ServletContext来间接建立当前应用与Servlet引擎的关联。
Spring容器ApplicationContext体系
-
WebServerApplicationContext接口:定义获取WebServer引用的方法
/** * Interface to be implemented by {@link ApplicationContext application contexts} that * create and manage the lifecycle of an embedded {@link WebServer}. * * @author Phillip Webb * @since 2.0.0 */ public interface WebServerApplicationContext extends ApplicationContext { /** * Returns the {@link WebServer} that was created by the context or {@code null} if * the server has not yet been created. * @return the web server */ WebServer getWebServer(); /** * Returns the namespace of the web server application context or {@code null} if no * namespace has been set. Used for disambiguation when multiple web servers are * running in the same application (for example a management context running on a * different port). * @return the server namespace */ String getServerNamespace(); }
-
ServletWebServerApplicationContext:WebServerApplicationContext接口的具体实现类,包含WebServer的引用,类定义如下:
/** * A {@link WebApplicationContext} that can be used to bootstrap itself from a contained * {@link ServletWebServerFactory} bean. * <p> * This context will create, initialize and run an {@link WebServer} by searching for a * single {@link ServletWebServerFactory} bean within the {@link ApplicationContext} * itself. The {@link ServletWebServerFactory} is free to use standard Spring concepts * (such as dependency injection, lifecycle callbacks and property placeholder variables). * <p> * In addition, any {@link Servlet} or {@link Filter} beans defined in the context will be * automatically registered with the web server. In the case of a single Servlet bean, the * '/' mapping will be used. If multiple Servlet beans are found then the lowercase bean * name will be used as a mapping prefix. Any Servlet named 'dispatcherServlet' will * always be mapped to '/'. Filter beans will be mapped to all URLs ('/*'). * <p> * For more advanced configuration, the context can instead define beans that implement * the {@link ServletContextInitializer} interface (most often * {@link ServletRegistrationBean}s and/or {@link FilterRegistrationBean}s). To prevent * double registration, the use of {@link ServletContextInitializer} beans will disable * automatic Servlet and Filter bean registration. * <p> * Although this context can be used directly, most developers should consider using the * {@link AnnotationConfigServletWebServerApplicationContext} or * {@link XmlServletWebServerApplicationContext} variants. * * @author Phillip Webb * @author Dave Syer * @see AnnotationConfigServletWebServerApplicationContext * @see XmlServletWebServerApplicationContext * @see ServletWebServerFactory */ public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext { private static final Log logger = LogFactory .getLog(ServletWebServerApplicationContext.class); /** * Constant value for the DispatcherServlet bean name. A Servlet bean with this name * is deemed to be the "main" servlet and is automatically given a mapping of "/" by * default. To change the default behavior you can use a * {@link ServletRegistrationBean} or a different bean name. */ public static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet"; private volatile WebServer webServer; private ServletConfig servletConfig; private String serverNamespace; ... }
- 由继承体系可知,继承了GenericWebApplicationContext,在GenericWebApplicationContext中定义了ServletContext引用。
-
由代码注释可知,Spring容器管理了一个ServletWebServerFactory引用,ServletWebServerFactory为WebServer实现类对象的工厂类,通过ServletWebServerFactory来创建servlet引擎WebServer,其中由ServletWebServerFactory创建的WebServer默认在8080端口监听请求,具体为在基类AbstractConfigurableWebServerFactory中定义。
- 同时注册到Spring容器的Servlet,Filter接口的实现类会自动注册到Servlet引擎,具体为应用对应的ServletContext。
-
AnnotationConfigServletWebServerApplicationContext:基于注解的Spring容器,继承了ServletWebServerApplicationContext。
-
XmlServletWebServerApplicationContext:基于XML的Spring容器,继承了ServletWebServerApplicationContext。
Servlet引擎体系
-
WebServer接口:声明Servlet引擎启动,关闭的方法,相应的实现类实现这些方法来定义启动和关闭逻辑,而对Spring容器ServletWebServerApplicationContext而言,只需依赖这个接口即可,不依赖具体实现,这也是遵循了依赖倒置设计原则。
/** * Simple interface that represents a fully configured web server (for example Tomcat, * Jetty, Netty). Allows the server to be {@link #start() started} and {@link #stop() * stopped}. * * @author Phillip Webb * @author Dave Syer * @since 2.0.0 */ public interface WebServer { /** * Starts the web server. Calling this method on an already started server has no * effect. * @throws WebServerException if the server cannot be started */ void start() throws WebServerException; /** * Stops the web server. Calling this method on an already stopped server has no * effect. * @throws WebServerException if the server cannot be stopped */ void stop() throws WebServerException; /** * Return the port this server is listening on. * @return the port (or -1 if none) */ int getPort(); }
-
ServletWebServerFactory接口:Servlet引擎WebServer的工厂接口,接口定义如下,声明了getWebServer方法。也是遵循依赖倒置设计原则,即Spring容器ServletWebServerApplicationContext只依赖这个接口,具体为这个接口的getWebServer方法来获取一个Servlet引擎WebServer对象,而该接口的具体实现类,负责实现这个接口。实现类包括:TomcatServletWebServerFactory,JettyServletWebServerFactory,UndertowServletWebServerFactory,具体在embedded包定义。
/** * Factory interface that can be used to create a {@link WebServer}. * * @author Phillip Webb * @see WebServer * @since 2.0.0 */ @FunctionalInterface public interface ServletWebServerFactory { /** * Gets a new fully configured but paused {@link WebServer} instance. Clients should * not be able to connect to the returned server until {@link WebServer#start()} is * called (which happens when the {@link ApplicationContext} has been fully * refreshed). * @param initializers {@link ServletContextInitializer}s that should be applied as * the server starts * @return a fully configured and started {@link WebServer} * @see WebServer#stop() */ WebServer getWebServer(ServletContextInitializer... initializers); }
Spring容器和Servlet引擎启动过程
-
Spring容器是在ApplicationContext的refresh方法定义启动流程的,具体为在AbstractApplicationContext中定义refresh方法的流程模板:关于Servlet引擎的启动,是在onRefresh和finishRefresh方法定义的,由onRefresh方法的注释可知,这个方法是设计用来注册有特殊功能的bean对象到Spring容器内部的BeanFactory的。所以Spring容器的设计拓展性是很好的。
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } ... } }
-
ServletWebServerApplicationContext的refresh方法,onRefresh方法,finishRefresh方法。其中onRefresh方法负责Server引擎和ServletContext的创建;finishOnRefresh方法负责Servlet引擎的启动,即调用WebServer的start方法,然后在指定的端口,如8080,监听客户端的请求。
@Override public final void refresh() throws BeansException, IllegalStateException { try { super.refresh(); } catch (RuntimeException ex) { stopAndReleaseWebServer(); throw ex; } } @Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } } @Override protected void finishRefresh() { super.finishRefresh(); WebServer webServer = startWebServer(); if (webServer != null) { publishEvent(new ServletWebServerInitializedEvent(webServer, this)); } }
-
createWebServer方法实现:创建servlet引擎WebServer和ServletContext。
private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { ServletWebServerFactory factory = getWebServerFactory(); // 创建servlet引擎WebServer和ServletContext this.webServer = factory.getWebServer(getSelfInitializer()); } else if (servletContext != null) { try { // 如果已经启动过,则将应用配置的 // Servlet规范的Servlet,Filter,Listener再初始化一次ServletContext getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }
-
startWebServer方法实现:调用Servlet引擎的start方法完成启动。
private WebServer startWebServer() { WebServer webServer = this.webServer; if (webServer != null) { webServer.start(); } return webServer; }
- 以下为TomcatWebServer的start方法实现:启动应用在Servlet规范中对应的Context,即TomcatEmbeddedContext
@Override public void start() throws WebServerException { synchronized (this.monitor) { if (this.started) { return; } try { addPreviouslyRemovedConnectors(); Connector connector = this.tomcat.getConnector(); if (connector != null && this.autoStart) { // 启动TomcatEmbeddedContext performDeferredLoadOnStartup(); } checkThatConnectorsHaveStarted(); this.started = true; logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '" + getContextPath() + "'"); } catch (ConnectorStartFailedException ex) { stopSilently(); throw ex; } catch (Exception ex) { throw new WebServerException("Unable to start embedded Tomcat server", ex); } finally { Context context = findContext(); ContextBindings.unbindClassLoader(context, context.getNamingToken(), getClass().getClassLoader()); } } } // 启动TomcatEmbeddedContext private void performDeferredLoadOnStartup() { try { for (Container child : this.tomcat.getHost().findChildren()) { if (child instanceof TomcatEmbeddedContext) { ((TomcatEmbeddedContext) child).deferredLoadOnStartup(); } } } catch (Exception ex) { if (ex instanceof WebServerException) { throw (WebServerException) ex; } throw new WebServerException("Unable to start embedded Tomcat connectors", ex); } }
- 以下为TomcatWebServer的start方法实现:启动应用在Servlet规范中对应的Context,即TomcatEmbeddedContext