本篇文章給大家分享的是有關(guān)如何在Spring Boot中內(nèi)嵌Tomcat并啟動(dòng),小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
為婁星等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計(jì)制作服務(wù),及婁星網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為成都做網(wǎng)站、成都網(wǎng)站建設(shè)、婁星網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!
createEmbeddedServletContainer創(chuàng)建了內(nèi)嵌的Servlet容器,我用的是默認(rèn)的Tomcat。
private void createEmbeddedServletContainer() { EmbeddedServletContainer localContainer = this.embeddedServletContainer; ServletContext localServletContext = getServletContext(); if (localContainer == null && localServletContext == null) { EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory(); this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer()); } else if (localServletContext != null) { try { getSelfInitializer().onStartup(localServletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }
getEmbeddedServletContainerFactory方法中調(diào)用了ServerProperties,從ServerProperties的實(shí)例方法customize可以看出Springboot支持三種內(nèi)嵌容器的定制化配置:Tomcat、Jetty、Undertow。
這里直接說TomcatEmbeddedServletContainerFactory的getEmbeddedServletContainer方法了,原因在前面那篇里說過了。不過首先是getSelfInitializer方法先執(zhí)行的:
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { return new ServletContextInitializer() { @Override public void onStartup(ServletContext servletContext) throws ServletException { selfInitialize(servletContext); } }; }
將初始化的ServletContextInitializer傳給了getEmbeddedServletContainer方法。進(jìn)入了getEmbeddedServletContainer方法直接就是實(shí)例化了一個(gè)Tomcat:
Tomcat tomcat = new Tomcat();
然后生成一個(gè)臨時(shí)目錄,并tomcat.setBaseDir,setBaseDir方法的注釋說Tomcat需要一個(gè)目錄用于臨時(shí)文件并且它應(yīng)該是第一個(gè)被調(diào)用的方法;如果方法沒有被調(diào)用會(huì)使用默認(rèn)的幾個(gè)位置system properties - catalina.base, catalina.home - $PWD/tomcat.$PORT,另外/tmp從安全角度來說不建議。
接著:
Connector connector = new Connector(this.protocol);
創(chuàng)建Connector過程中,靜態(tài)代碼塊:?jiǎn)为?dú)抽出來寫了。RECYCLE_FACADES屬性可以通過啟動(dòng)參數(shù)JAVA_OPTS來配置: -Dorg.apache.catalina.connector.RECYCLE_FACADES=,默認(rèn)是false,配置成true可以提高安全性但同時(shí)性能會(huì)有些損耗,參考:http://tomcat.apache.org/tomcat-7.0-doc/config/systemprops.html和http://bztax.gov.cn/docs/security-howto.html。其他屬性不細(xì)說了,Connector構(gòu)造的邏輯主要是在NIO和APR選擇中選擇一個(gè)協(xié)議,我的是org.apache.coyote.http11.Http11NioProtocol,然后反射創(chuàng)建實(shí)例并強(qiáng)轉(zhuǎn)為ProtocolHandler。關(guān)于apr,似乎是更native,性能據(jù)說更好,但我沒測(cè),相關(guān)文檔可參考:http://tomcat.apache.org/tomcat-8.5-doc/apr.html。這里簡(jiǎn)單提一下coyote,它的主要作用是將socket接受的信息封裝為request和response并提供給上Servlet容器,進(jìn)行上下層之間的溝通,文檔我沒找到比較新的:http://tomcat.apache.org/tomcat-4.1-doc/config/coyote.html。STRICT_SERVLET_COMPLIANCE也是啟動(dòng)參數(shù)控制,默認(rèn)是false,配置項(xiàng)是org.apache.catalina.STRICT_SERVLET_COMPLIANCE,默認(rèn)情況下會(huì)設(shè)置URIEncoding = "UTF-8"和URIEncodingLower = URIEncoding.toLowerCase(Locale.ENGLISH),相關(guān)詳細(xì)介紹可參考:http://tomcat.apache.org/tomcat-7.0-doc/config/systemprops.html。Connector的創(chuàng)建過程比較關(guān)鍵,容我單獨(dú)寫一篇吧。
Connector實(shí)例創(chuàng)建好了之后tomcat.getService().addConnector(connector),getService的getServer中new了一個(gè)StandardServer,StandardServer的初始化主要是創(chuàng)建了globalNamingResources(globalNamingResources主要用于管理明明上下文和JDNI上下文),并根據(jù)catalina.useNaming判斷是否注冊(cè)NamingContextListener監(jiān)聽器給lifecycleListeners。創(chuàng)建Server之后initBaseDir,先讀取catalina.home配置System.getProperty(Globals.CATALINA_BASE_PROP),如果沒取到則使用之前生成的臨時(shí)目錄,這段直接看代碼吧:
protected void initBaseDir() { String catalinaHome = System.getProperty(Globals.CATALINA_HOME_PROP); if (basedir == null) { basedir = System.getProperty(Globals.CATALINA_BASE_PROP); } if (basedir == null) { basedir = catalinaHome; } if (basedir == null) { // Create a temp dir. basedir = System.getProperty("user.dir") + "/tomcat." + port; } File baseFile = new File(basedir); baseFile.mkdirs(); try { baseFile = baseFile.getCanonicalFile(); } catch (IOException e) { baseFile = baseFile.getAbsoluteFile(); } server.setCatalinaBase(baseFile); System.setProperty(Globals.CATALINA_BASE_PROP, baseFile.getPath()); basedir = baseFile.getPath(); if (catalinaHome == null) { server.setCatalinaHome(baseFile); } else { File homeFile = new File(catalinaHome); homeFile.mkdirs(); try { homeFile = homeFile.getCanonicalFile(); } catch (IOException e) { homeFile = homeFile.getAbsoluteFile(); } server.setCatalinaHome(homeFile); } System.setProperty(Globals.CATALINA_HOME_PROP, server.getCatalinaHome().getPath()); }
然后又實(shí)例化了個(gè)StandardService,代碼并沒有什么特別的:
service = new StandardService(); service.setName("Tomcat"); server.addService( service )
server.addService( service )這里除了發(fā)布了一個(gè)PropertyChangeEvent事件,也沒做什么特別的,最后返回這個(gè)server。addConnector的邏輯和上面addService沒什么區(qū)別。然后是customizeConnector,這里設(shè)置了Connector的端口、編碼等信息,并將“bindOnInit”和對(duì)應(yīng)值false寫入了最開頭說的靜態(tài)代碼塊中的replacements集合,IntrospectionUtils.setProperty(protocolHandler, repl, value)通過反射的方法將protocolHandler實(shí)現(xiàn)對(duì)象的setBindOnInit存在的情況下(拼字符串拼出來的)set為前面的false,這個(gè)方法里有大量的判斷比如參數(shù)類型及setter的參數(shù)類型,比如返回值類型以及沒找到還會(huì)try a setProperty("name", "value")等,setProperty可以處理比如AbstractEndpoint中有個(gè)HashMap<String, Object> attributes的屬性時(shí)會(huì)attributes.put(name, value)。如果是ssl還會(huì)執(zhí)行customizeSsl方法,設(shè)置一些SSL用的屬性比如協(xié)議比如秘鑰還有可以用上秘鑰倉庫等。如果配置了壓縮,這里還會(huì)給協(xié)議的相關(guān)setter設(shè)置值。tomcat.setConnector(connector)不解釋。tomcat.getHost().setAutoDeploy(false),getHost方法中創(chuàng)建了StandardHost并設(shè)置host名(例如localhost),并getEngine().addChild( host );然后設(shè)置host的自動(dòng)部署。configureEngine(tomcat.getEngine()),getEngine中如果engine為null就初始化標(biāo)準(zhǔn)引擎,設(shè)置名字為Tomcat,設(shè)置Realm和service.setContainer(engine),不過這里engine已經(jīng)在getHost初始化過了所以直接返回;configureEngine方法先設(shè)置引擎的后臺(tái)進(jìn)程延遲,并將引擎的Value對(duì)象注冊(cè)給引擎的pipeline,此時(shí)尚無value對(duì)象實(shí)例。這里簡(jiǎn)單說明一下:value對(duì)象在Tomcat的各級(jí)容器中都有標(biāo)準(zhǔn)類型,并且各級(jí)容器都有一個(gè)pipeline,在請(qǐng)求處理過程中會(huì)從各級(jí)的第一個(gè)value對(duì)象開始依次執(zhí)行一遍,value用于加入到對(duì)應(yīng)的各級(jí)容器的邏輯,默認(rèn)有一個(gè)標(biāo)注value實(shí)現(xiàn),名字類似StandardHostValue。
prepareContext(tomcat.getHost(), initializers),initializers這里是AnnotationConfigEmbeddedWebApplicationContext,Context級(jí)的根;準(zhǔn)備Context的過程主要設(shè)置Base目錄,new一個(gè)TomcatEmbeddedContext并在構(gòu)造中判斷了下loadOnStartup方法是否被重寫;注冊(cè)一個(gè)FixContextListener監(jiān)聽,這個(gè)監(jiān)聽用于設(shè)置context的配置狀態(tài)以及是否加入登錄驗(yàn)證的邏輯;context.setParentClassLoader;設(shè)置各種語言的編碼映射,我這里是en和fr設(shè)置為UTF-8,此處可以使用配置文件org/apache/catalina/util/CharsetMapperDefault .properties;設(shè)置是否使用相對(duì)地址重定向useRelativeRedirects=false,此屬性應(yīng)該是Tomcat 8.0.30版本加上的;接著就是初始化webapploader,這里和完整版的Tomcat有點(diǎn)不一樣,它用的是虛擬機(jī)的方式,會(huì)將加載類向上委托l(wèi)oader.setDelegate(true),context.setLoader(loader);之后就開始創(chuàng)建Wapper了,至此engine,host,context及wrapper四個(gè)層次的容器都創(chuàng)建完了:
private void addDefaultServlet(Context context) { Wrapper defaultServlet = context.createWrapper(); defaultServlet.setName("default"); defaultServlet.setServletClass("org.apache.catalina.servlets.DefaultServlet"); defaultServlet.addInitParameter("debug", "0"); defaultServlet.addInitParameter("listings", "false"); defaultServlet.setLoadOnStartup(1); // Otherwise the default location of a Spring DispatcherServlet cannot be set defaultServlet.setOverridable(true); context.addChild(defaultServlet); addServletMapping(context, "/", "default"); }
connector從socket接收的數(shù)據(jù),解析成HttpServletRequest后就會(huì)經(jīng)過這幾層容器,有容器各自的Value對(duì)象鏈依次處理。
接著是是否注冊(cè)jspServlet,jasperInitializer和StoreMergedWebXmlListener我這里是都沒有的。接著的mergeInitializers方法:
protected final ServletContextInitializer[] mergeInitializers( ServletContextInitializer... initializers) { List<ServletContextInitializer> mergedInitializers = new ArrayList<ServletContextInitializer>(); mergedInitializers.addAll(Arrays.asList(initializers)); mergedInitializers.addAll(this.initializers); return mergedInitializers .toArray(new ServletContextInitializer[mergedInitializers.size()]); }
configureContext(context, initializersToUse)對(duì)context做了些設(shè)置工作,包括TomcatStarter(實(shí)例化并set給context),LifecycleListener,contextValue,errorpage,Mime,session超時(shí)持久化等以及一些自定義工作:
TomcatStarter starter = new TomcatStarter(initializers); if (context instanceof TomcatEmbeddedContext) { // Should be true ((TomcatEmbeddedContext) context).setStarter(starter); } context.addServletContainerInitializer(starter, NO_CLASSES); for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) { context.addLifecycleListener(lifecycleListener); } for (Valve valve : this.contextValves) { context.getPipeline().addValve(valve); } for (ErrorPage errorPage : getErrorPages()) { new TomcatErrorPage(errorPage).addToContext(context); } for (MimeMappings.Mapping mapping : getMimeMappings()) { context.addMimeMapping(mapping.getExtension(), mapping.getMimeType()); }
Session如果不需要持久化會(huì)注冊(cè)一個(gè)DisablePersistSessionListener。其他定制化操作是通過TomcatContextCustomizer的實(shí)現(xiàn)類實(shí)現(xiàn)的:
context配置完了作為child add給host,add時(shí)給context注冊(cè)了個(gè)內(nèi)存泄漏跟蹤的監(jiān)聽MemoryLeakTrackingListener。postProcessContext(context)方法是空的,留給子類重寫用的。
getEmbeddedServletContainer方法的最后一行:return getTomcatEmbeddedServletContainer(tomcat)。
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer( Tomcat tomcat) { return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0); }
TomcatEmbeddedServletContainer的構(gòu)造函數(shù):
public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; initialize(); }
initialize的第一個(gè)方法addInstanceIdToEngineName對(duì)全局原子變量containerCounter+1,由于初始值是-1,所以addInstanceIdToEngineName方法內(nèi)后續(xù)的獲取引擎并設(shè)置名字的邏輯沒有執(zhí)行:
private void addInstanceIdToEngineName() { int instanceId = containerCounter.incrementAndGet(); if (instanceId > 0) { Engine engine = this.tomcat.getEngine(); engine.setName(engine.getName() + "-" + instanceId); } }
initialize的第二個(gè)方法removeServiceConnectors,將上面new的connection以service(這里是StandardService[Tomcat])做key保存到private final Map<Service, Connector[]> serviceConnectors中,并將StandardService中的protected Connector[] connectors與service解綁(connector.setService((Service)null);),解綁后下面利用LifecycleBase啟動(dòng)容器就不會(huì)啟動(dòng)到Connector了。
之后是this.tomcat.start(),這段比較復(fù)雜,我單獨(dú)總結(jié)一篇吧。
TomcatEmbeddedServletContainer的初始化,接下來是rethrowDeferredStartupExceptions,這個(gè)方法檢查初始化過程中的異常,如果有直接在主線程拋出,檢查方法是TomcatStarter中的private volatile Exception startUpException,這個(gè)值是在Context啟動(dòng)過程中記錄的:
@Override public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException { try { for (ServletContextInitializer initializer : this.initializers) { initializer.onStartup(servletContext); } } catch (Exception ex) { this.startUpException = ex; // Prevent Tomcat from logging and re-throwing when we know we can // deal with it in the main thread, but log for information here. if (logger.isErrorEnabled()) { logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: " + ex.getMessage()); } } }
Context context = findContext():
private Context findContext() { for (Container child : this.tomcat.getHost().findChildren()) { if (child instanceof Context) { return (Context) child; } } throw new IllegalStateException("The host does not contain a Context"); }
綁定命名的上下文和classloader,不成功也無所謂:
try { ContextBindings.bindClassLoader(context, getNamingToken(context), getClass().getClassLoader()); } catch (NamingException ex) { // Naming is not enabled. Continue }
startDaemonAwaitThread方法的注釋是:與Jetty不同,Tomcat所有的線程都是守護(hù)線程,所以創(chuàng)建一個(gè)非守護(hù)線程(例:Thread[container-0,5,main])來避免服務(wù)到這就shutdown了:
private void startDaemonAwaitThread() { Thread awaitThread = new Thread("container-" + (containerCounter.get())) { @Override public void run() { TomcatEmbeddedServletContainer.this.tomcat.getServer().await(); } }; awaitThread.setContextClassLoader(getClass().getClassLoader()); awaitThread.setDaemon(false); awaitThread.start(); }
這個(gè)await每10秒檢查一次是否關(guān)閉了:
try { awaitThread = Thread.currentThread(); while(!stopAwait) { try { Thread.sleep( 10000 ); } catch( InterruptedException ex ) { // continue and check the flag } } } finally { awaitThread = null; } return;
回到EmbeddedWebApplicationContext,initPropertySources方法,用初始化好的servletContext完善環(huán)境變量:
/** * {@inheritDoc} * <p>Replace {@code Servlet}-related property sources. */ @Override protected void initPropertySources() { ConfigurableEnvironment env = getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, null); } }
createEmbeddedServletContainer就結(jié)束了,內(nèi)嵌容器的啟動(dòng)過程至此結(jié)束。
以上就是如何在Spring Boot中內(nèi)嵌Tomcat并啟動(dòng),小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見到或用到的。希望你能通過這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
本文標(biāo)題:如何在SpringBoot中內(nèi)嵌Tomcat并啟動(dòng)
URL標(biāo)題:http://chinadenli.net/article26/ihcocg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供虛擬主機(jī)、定制網(wǎng)站、動(dòng)態(tài)網(wǎng)站、網(wǎng)站維護(hù)、、網(wǎng)站排名
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)