国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

Spring Boot啟動過程(四)之Spring Boot內嵌Tomcat啟動

2019-11-26 12:30:50
字體:
來源:轉載
供稿:網友

之前在Spring Boot啟動過程(二)提到過createEmbeddedServletContainer創建了內嵌的Servlet容器,我用的是默認的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方法中調用了ServerProperties,從ServerProperties的實例方法customize可以看出Springboot支持三種內嵌容器的定制化配置:Tomcat、Jetty、Undertow。

  這里直接說TomcatEmbeddedServletContainerFactory的getEmbeddedServletContainer方法了,原因在前面那篇里說過了。不過首先是getSelfInitializer方法先執行的:

 private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { return new ServletContextInitializer() {  @Override  public void onStartup(ServletContext servletContext) throws ServletException {  selfInitialize(servletContext);  } }; }

  將初始化的ServletContextInitializer傳給了getEmbeddedServletContainer方法。進入了getEmbeddedServletContainer方法直接就是實例化了一個Tomcat:

 Tomcat tomcat = new Tomcat();

   然后生成一個臨時目錄,并tomcat.setBaseDir,setBaseDir方法的注釋說Tomcat需要一個目錄用于臨時文件并且它應該是第一個被調用的方法;如果方法沒有被調用會使用默認的幾個位置system properties - catalina.base, catalina.home - $PWD/tomcat.$PORT,另外/tmp從安全角度來說不建議。

  接著:

 Connector connector = new Connector(this.protocol);

  創建Connector過程中,靜態代碼塊:單獨抽出來寫了。RECYCLE_FACADES屬性可以通過啟動參數JAVA_OPTS來配置: -Dorg.apache.catalina.connector.RECYCLE_FACADES=,默認是false,配置成true可以提高安全性但同時性能會有些損耗,參考:http://tomcat.apache.org/tomcat-7.0-doc/config/systemprops.htmlhttp://bztax.gov.cn/docs/security-howto.html。其他屬性不細說了,Connector構造的邏輯主要是在NIO和APR選擇中選擇一個協議,我的是org.apache.coyote.http11.Http11NioProtocol,然后反射創建實例并強轉為ProtocolHandler。關于apr,似乎是更native,性能據說更好,但我沒測,相關文檔可參考:http://tomcat.apache.org/tomcat-8.5-doc/apr.html。這里簡單提一下coyote,它的主要作用是將socket接受的信息封裝為request和response并提供給上Servlet容器,進行上下層之間的溝通,文檔我沒找到比較新的:http://tomcat.apache.org/tomcat-4.1-doc/config/coyote.html。STRICT_SERVLET_COMPLIANCE也是啟動參數控制,默認是false,配置項是org.apache.catalina.STRICT_SERVLET_COMPLIANCE,默認情況下會設置URIEncoding = "UTF-8"和URIEncodingLower = URIEncoding.toLowerCase(Locale.ENGLISH),相關詳細介紹可參考:http://tomcat.apache.org/tomcat-7.0-doc/config/systemprops.html。Connector的創建過程比較關鍵,容我單獨寫一篇吧。

  Connector實例創建好了之后tomcat.getService().addConnector(connector),getService的getServer中new了一個StandardServer,StandardServer的初始化主要是創建了globalNamingResources(globalNamingResources主要用于管理明明上下文和JDNI上下文),并根據catalina.useNaming判斷是否注冊NamingContextListener監聽器給lifecycleListeners。創建Server之后initBaseDir,先讀取catalina.home配置System.getProperty(Globals.CATALINA_BASE_PROP),如果沒取到則使用之前生成的臨時目錄,這段直接看代碼吧:

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()); }

   然后又實例化了個StandardService,代碼并沒有什么特別的:

 service = new StandardService(); service.setName("Tomcat"); server.addService( service )

  server.addService( service )這里除了發布了一個PropertyChangeEvent事件,也沒做什么特別的,最后返回這個server。addConnector的邏輯和上面addService沒什么區別。然后是customizeConnector,這里設置了Connector的端口、編碼等信息,并將“bindOnInit”和對應值false寫入了最開頭說的靜態代碼塊中的replacements集合,IntrospectionUtils.setProperty(protocolHandler, repl, value)通過反射的方法將protocolHandler實現對象的setBindOnInit存在的情況下(拼字符串拼出來的)set為前面的false,這個方法里有大量的判斷比如參數類型及setter的參數類型,比如返回值類型以及沒找到還會try a setProperty("name", "value")等,setProperty可以處理比如AbstractEndpoint中有個HashMap<String, Object> attributes的屬性時會attributes.put(name, value)。如果是ssl還會執行customizeSsl方法,設置一些SSL用的屬性比如協議比如秘鑰還有可以用上秘鑰倉庫等。如果配置了壓縮,這里還會給協議的相關setter設置值。tomcat.setConnector(connector)不解釋。tomcat.getHost().setAutoDeploy(false),getHost方法中創建了StandardHost并設置host名(例如localhost),并getEngine().addChild( host );然后設置host的自動部署。configureEngine(tomcat.getEngine()),getEngine中如果engine為null就初始化標準引擎,設置名字為Tomcat,設置Realm和service.setContainer(engine),不過這里engine已經在getHost初始化過了所以直接返回;configureEngine方法先設置引擎的后臺進程延遲,并將引擎的Value對象注冊給引擎的pipeline,此時尚無value對象實例。這里簡單說明一下:value對象在Tomcat的各級容器中都有標準類型,并且各級容器都有一個pipeline,在請求處理過程中會從各級的第一個value對象開始依次執行一遍,value用于加入到對應的各級容器的邏輯,默認有一個標注value實現,名字類似StandardHostValue。

  prepareContext(tomcat.getHost(), initializers),initializers這里是AnnotationConfigEmbeddedWebApplicationContext,Context級的根;準備Context的過程主要設置Base目錄,new一個TomcatEmbeddedContext并在構造中判斷了下loadOnStartup方法是否被重寫;注冊一個FixContextListener監聽,這個監聽用于設置context的配置狀態以及是否加入登錄驗證的邏輯;context.setParentClassLoader;設置各種語言的編碼映射,我這里是en和fr設置為UTF-8,此處可以使用配置文件org/apache/catalina/util/CharsetMapperDefault .properties;設置是否使用相對地址重定向useRelativeRedirects=false,此屬性應該是Tomcat 8.0.30版本加上的;接著就是初始化webapploader,這里和完整版的Tomcat有點不一樣,它用的是虛擬機的方式,會將加載類向上委托loader.setDelegate(true),context.setLoader(loader);之后就開始創建Wapper了,至此engine,host,context及wrapper四個層次的容器都創建完了:

 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接收的數據,解析成HttpServletRequest后就會經過這幾層容器,有容器各自的Value對象鏈依次處理。

  接著是是否注冊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)對context做了些設置工作,包括TomcatStarter(實例化并set給context),LifecycleListener,contextValue,errorpage,Mime,session超時持久化等以及一些自定義工作:    

 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如果不需要持久化會注冊一個DisablePersistSessionListener。其他定制化操作是通過TomcatContextCustomizer的實現類實現的:

  context配置完了作為child add給host,add時給context注冊了個內存泄漏跟蹤的監聽MemoryLeakTrackingListener。postProcessContext(context)方法是空的,留給子類重寫用的。

   getEmbeddedServletContainer方法的最后一行:return getTomcatEmbeddedServletContainer(tomcat)。

protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(  Tomcat tomcat) { return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0); }

   TomcatEmbeddedServletContainer的構造函數:

 public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; initialize(); }

  initialize的第一個方法addInstanceIdToEngineName對全局原子變量containerCounter+1,由于初始值是-1,所以addInstanceIdToEngineName方法內后續的獲取引擎并設置名字的邏輯沒有執行:

 private void addInstanceIdToEngineName() { int instanceId = containerCounter.incrementAndGet(); if (instanceId > 0) {  Engine engine = this.tomcat.getEngine();  engine.setName(engine.getName() + "-" + instanceId); } }

   initialize的第二個方法removeServiceConnectors,將上面new的connection以service(這里是StandardService[Tomcat])做key保存到private final Map<Service, Connector[]> serviceConnectors中,并將StandardService中的protected Connector[] connectors與service解綁(connector.setService((Service)null);),解綁后下面利用LifecycleBase啟動容器就不會啟動到Connector了。

  之后是this.tomcat.start(),這段比較復雜,我單獨總結一篇吧。

  TomcatEmbeddedServletContainer的初始化,接下來是rethrowDeferredStartupExceptions,這個方法檢查初始化過程中的異常,如果有直接在主線程拋出,檢查方法是TomcatStarter中的private volatile Exception startUpException,這個值是在Context啟動過程中記錄的:

 @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所有的線程都是守護線程,所以創建一個非守護線程(例:Thread[container-0,5,main])來避免服務到這就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(); }

  這個await每10秒檢查一次是否關閉了:        

 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完善環境變量:

 /** * {@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就結束了,內嵌容器的啟動過程至此結束。

==========================================================

咱最近用的github:https://github.com/saaavsaaa

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 永登县| 通辽市| 内乡县| 弥渡县| 平阴县| 台州市| 巴南区| 大洼县| 昌邑市| 文昌市| 郑州市| 杭州市| 天峻县| 石嘴山市| 忻州市| 南涧| 通江县| 行唐县| 百色市| 扶余县| 栖霞市| 海盐县| 雷山县| 丘北县| 柳林县| 阜新市| 莲花县| 江陵县| 兴业县| 厦门市| 高唐县| 鄄城县| 峨眉山市| 台山市| 永兴县| 车险| 海伦市| 惠东县| 舞阳县| 巴东县| 恩平市|