传统项目为了启动,会放在tomcat下面,那么springboot为何不需要放在tomcat启动呢??因为springboot有内置tomcat启动项目,这篇文章从源码分析springboot如何启动内置tomcat。
// 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();
registerBeanPostProcessors我们onRefresh就是实现了这个类,前面的postBeanFactory是对容器扩展,这里的是对bean的对象进行扩展,有着beanPostProcessorBeaforeInstantiation和beanPostProcessorAfterInstantiation后置处理器,保证单实例 和 实例化完毕。
initApplicationEventMuliticaster是注册广播对象到容器,在实际代码开发中,会和applicationEvent和applicationListener使用。
熟悉了这里面的大致逻辑之后,我们这篇文章主要介绍onRefresh()方法,核心方法在这里:
//ServletWebServerApplicationContext.java @Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } } //父类的onRefresh @Override protected void onRefresh() { this.themeSource = UiApplicationContextUtils.initThemeSource(this); }
他super的父类没什么逻辑,主要看这个createWenServer():
private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { ServletWebServerFactory factory = getWebServerFactory(); this.webServer = factory.getWebServer(getSelfInitializer()); getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer)); getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer)); } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }
这里面逻辑主要是当servletContext为空或者webServer为空,则就通过servletWebServerFactory创建一个webServer,或者走else if 里面的onStartup方法,最后都会执行initPropeertySources()。
那这几个组件是什么呢,首先上面说了servletWebServerFactory就是管理webServer的工厂,用来创建webServer。
servletContext指整个web请求是上下文对象,在tomcat中通常整个请求的上下文都封装在这个对象中。
webServer则是核心的组件,里面可以看到封装了web容器的启动和停止,获取端口的核心操作,也就是webServer是web容器的抽象封装。
@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 {@code 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); } 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(); /** * Initiates a graceful shutdown of the web server. Handling of new requests is * prevented and the given {@code callback} is invoked at the end of the attempt. The * attempt can be explicitly ended by invoking {@link #stop}. The default * implementation invokes the callback immediately with * {@link GracefulShutdownResult#IMMEDIATE}, i.e. no attempt is made at a graceful * shutdown. * @param callback the callback to invoke when the graceful shutdown completes * @since 2.3.0 */ default void shutDownGracefully(GracefulShutdownCallback callback) { callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE); } }
getWebServer方法上面的注释翻译就是:
获取一个新的完全配置但暂停的{@link WebServer}实例。客户应不能连接到返回的服务器,直到{@link WebServer#start()}是调用(当{@code ApplicationContext}被完全调用时发生刷新)。
也就是说获取一个配置完成的webServer实例,当调用start方法的时候,applicationContext容器也就是spring容器已经刷新。
也就是说ServletWebServerFactory可以可以获取一个webServer,webServer可以通过start和stop操作容器。也就是springboot对web容器的抽象封装成为了webServer。
默认的我们会进入tomcatServletWebServerFactory里:
@Override public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.disableMBeanRegistry) { Registry.disableRegistry(); } 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); tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } prepareContext(tomcat.getHost(), initializers); return getTomcatWebServer(tomcat); }
1)这里方法的入参是jdk1.8引入的函数表达式,这个表达式放了这个对象的数组,这个数组传进来的接口就是ServletContextInitializer。
2)创建了核心的tomcat组件。
3)创建了connector,newConnector,以及springboot特有的coustomizeConnector。
4)通过configureEngine配置了tomcat的引擎。
5)准备tomcat和context相关的属性。
6)真正的启动tomcat。
点开new 的tomcat我们可以看到,里面有port端口号,hostname:localhost,是不是都非常熟悉。
1)还有wrapper相关操作的方法,比如addServlet方法就是返回一个wrapper。
2)有一堆组件的get方法,比如前面的engine、service、host,connection。
3)有一些context相关的方法,比如createContext。
基本可以看到,这个tomcat类封装了几乎tomcat的所有核心组件。
下面看connector的基本创建和扩展:
public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol"; private String protocol = DEFAULT_PROTOCOL; public WebServer getWebServer(ServletContextInitializer... initializers) { //其他 Tomcat tomcat = new Tomcat(); tomcat.setBaseDir(baseDir.getAbsolutePath()); Connector connector = new Connector(this.protocol); connector.setThrowOnFailure(true); tomcat.getService().addConnector(connector); customizeConnector(connector); //其他 }
这里可以看到connector传递的参数是上面的protocol,这个名称为什么用全名呢,我们从构造函数源码的 反射就可以看到:
public static ProtocolHandler create(String protocol, boolean apr) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { if (protocol == null || "HTTP/1.1".equals(protocol) || (!apr && org.apache.coyote.http11.Http11NioProtocol.class.getName().equals(protocol)) || (apr && org.apache.coyote.http11.Http11AprProtocol.class.getName().equals(protocol))) { if (apr) { return new org.apache.coyote.http11.Http11AprProtocol(); } else { return new org.apache.coyote.http11.Http11NioProtocol(); } } else if ("AJP/1.3".equals(protocol) || (!apr && org.apache.coyote.ajp.AjpNioProtocol.class.getName().equals(protocol)) || (apr && org.apache.coyote.ajp.AjpAprProtocol.class.getName().equals(protocol))) { if (apr) { return new org.apache.coyote.ajp.AjpAprProtocol(); } else { return new org.apache.coyote.ajp.AjpNioProtocol(); } } else { // Instantiate protocol handler Class<?> clazz = Class.forName(protocol); return (ProtocolHandler) clazz.getConstructor().newInstance(); } }
也就是说new connection核心就是创建了http的组件,这里还有个扩展的关键点,就是
customizeConnector()方法,这里面有给connector配置很多属性和配置。 // Needs to be protected so it can be used by subclasses protected void customizeConnector(Connector connector) { int port = Math.max(getPort(), 0); connector.setPort(port); if (StringUtils.hasText(getServerHeader())) { connector.setProperty("server", getServerHeader()); } if (connector.getProtocolHandler() instanceof AbstractProtocol) { customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler()); } invokeProtocolHandlerCustomizers(connector.getProtocolHandler()); if (getUriEncoding() != null) { connector.setURIEncoding(getUriEncoding().name()); } // Don't bind to the socket prematurely if ApplicationContext is slow to start connector.setProperty("bindOnInit", "false"); if (getSsl() != null && getSsl().isEnabled()) { customizeSsl(connector); } TomcatConnectorCustomizer compression = new CompressionConnectorCustomizer(getCompression()); compression.customize(connector); for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) { customizer.customize(connector); } } private void customizeProtocol(AbstractProtocol<?> protocol) { if (getAddress() != null) { protocol.setAddress(getAddress()); } }
通过上面可以看到对protocolHandler和connector进行了扩展,分别同的对应方法是invokeProtocoHandlerCustomizers和customizer.customize方法。这里面又跟这两个属性有关,
tomcatProtocolHandlerCustomizers和tomcatConnectorCustomizers
仔细可以看到这两个参数属于谁,TomcatServletWebServerFactory,前面说了webServer是通过ServletWebServerFactory获取的,那么这个TomcatServletWebServerFactory是哪里获取的呢,答案就是前面的onRefresh()。
protected ServletWebServerFactory getWebServerFactory() { // Use bean names so that we don't consider the hierarchy String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class); if (beanNames.length == 0) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing " + "ServletWebServerFactory bean."); } if (beanNames.length > 1) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple " + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames)); } return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class); }
其实从源码可以看到,虽然是从spring工厂按servletWebServerFactory类型查找bean的name,但是获取到的是beanDefinition。不过没关系,getBean容器中如果没有beanDefinition,它会进行bean实例化。那么bean的实例化会做什么呢,也就是servletWebServerFactory实例化会做什么呢,其实除了构造器之外,会给其扩展设置很多属性,而tomcatProtocolHandlerCustomizers和tomcatConnectorCustomizers就是他的属性,从而进行赋值。
大体就是初始化protocol相关的配置,比如setMaxThreads默认200、minSpareThreads默认10、maxHttpHeaderSize默认8192byte、maxSwallowSize 2097152等等。
熟悉了这个扩展点的逻辑后,其实最关键的是如何使用它,你可以通过ServerProperties扩展配置值,也可以自定义tomcatConnectorCustomizers或者tomcatProtocolHandlerCustomizers,只要实现对应的接口就可以了。这个才是领悟了SpringBoot的设计思路后最关键的。
TOMCAT的engine、context、host、wrapper关系
分析完connector组件后,再看看tomcat里面的组件建立关系,他们的关系也并不复杂,就是tomcat的基础知识,每个组件负责特定的事件处理。
springboot也对他们进行了扩展,比如对engine和context的阀门扩展,也是通过engineValues和contextValues进行扩展的。
//TomcatServletWebServerFactory.java private List<Valve> engineValves = new ArrayList<>(); private List<Valve> contextValves = new ArrayList<>();
springboot主要用TomcatServletWebServerFactory对tomcat进行封装和扩展的。
下面就开始看prepareContext预处理servletContext:
protected void prepareContext(Host host, ServletContextInitializer[] initializers) { File documentRoot = getValidDocumentRoot(); TomcatEmbeddedContext context = new TomcatEmbeddedContext(); if (documentRoot != null) { context.setResources(new LoaderHidingResourceRoot(context)); } context.setName(getContextPath()); context.setDisplayName(getDisplayName()); context.setPath(getContextPath()); File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase"); context.setDocBase(docBase.getAbsolutePath()); context.addLifecycleListener(new FixContextListener()); context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader() : ClassUtils.getDefaultClassLoader()); resetDefaultLocaleMapping(context); addLocaleMappings(context); try { context.setCreateUploadTargets(true); } catch (NoSuchMethodError ex) { // Tomcat is < 8.5.39. Continue. } configureTldPatterns(context); WebappLoader loader = new WebappLoader(); loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName()); loader.setDelegate(true); context.setLoader(loader); if (isRegisterDefaultServlet()) { addDefaultServlet(context); } if (shouldRegisterJspServlet()) { addJspServlet(context); addJasperInitializer(context); } context.addLifecycleListener(new StaticResourceConfigurer(context)); ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); host.addChild(context); configureContext(context, initializersToUse); postProcessContext(context); }
1)new TomcatEmbeddedContext
2)为tomcat的这个Context设置了很多值
3)执行了一个扩展点ServletContextInitializer
下面我们来看看ServletContextInitializer怎么执行的,我们可以看到这行代码:
//ServletWebServerApplicationContext.java private void selfInitialize(ServletContext servletContext) throws ServletException { prepareWebApplicationContext(servletContext); registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext); for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); } }
这里面主要有四个实现:
result = {ServletContextInitializerBeans@6345} size = 4 0 = {DispatcherServletRegistrationBean@6339} "dispatcherServlet urls=[/]" 1 = {FilterRegistrationBean@6350} "characterEncodingFilter urls=[/*] order=-2147483648" 2 = {FilterRegistrationBean@6351} "formContentFilter urls=[/*] order=-9900" 3 = {FilterRegistrationBean@6352} "requestContextFilter urls=[/*] order=-105"
完整的分析webServlet创建之后,基本就知道了springboot如何整合的tomcat,里面有个tomcatServletWebServerFactory可以创建WebServer,最终怎么启动的呢?
webServcer里面的方法有个strat(),调用之后才会真正启动。
/TomcatServletWebServerFactory.java @Override public WebServer getWebServer(ServletContextInitializer... initializers) { //省略 Tomcat的创建、connector的创建和扩展、其他组件的创建、prepareContext的执行和扩展 return getTomcatWebServer(tomcat); } protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { return new TomcatWebServer(tomcat, getPort() >= 0); } public TomcatWebServer(Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; initialize(); } private void initialize() throws WebServerException { logger.info("Tomcat initialized with port(s): " + getPortsDescription(false)); synchronized (this.monitor) { try { addInstanceIdToEngineName(); Context context = findContext(); context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) { // Remove service connectors so that protocol binding doesn't // happen when the service is started. removeServiceConnectors(); } }); // Start the server to trigger initialization listeners this.tomcat.start(); // We can re-throw failure exception directly in the main thread rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader()); } catch (NamingException ex) { // Naming is not enabled. Continue } // Unlike Jetty, all Tomcat threads are daemon threads. We create a // blocking non-daemon to stop immediate shutdown startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); destroySilently(); throw new WebServerException("Unable to start embedded Tomcat", ex); } } }
上面方法逻辑看似多,其实最关键的就一句话。这里核心是你抓大放小,主要关注一句话就可以了:
tomcat.start();
这个start方法执行的流程很有意思。它是类似一个链式调用。
其实你从之前tomcat的组件图就可以猜到,它们组件层级关系很多,每个组件都会触发下一层组件的逻辑。
每个组件都有生命周期,比如init方法-->start()-->destory()之类的。
那么也就说tomcat会以链的方式逐级调用各个模块的init()方法进行初始化, 待各个模块都初始化后, 又会逐级调用各个模块的start()方法启动各个模块。