Tomcat 做爲 servlet 容器實現,它是基於 Java 語言開發的輕量級應用服務器。由於 Tomcat 做爲應用服務器,它有着徹底開源,輕量,性能穩定,部署成本低等優勢,因此它成爲目前 Java 開發應用部署的首選,幾乎每一個Java Web開發者都有使用過,可是,你對 Tomcat 的總體設計有進行過了解和思考嗎?java
本文將基於 Tomcat8 進行分析,具體版本爲 Tomcat8 當前官網最新修改(2019-11-21 09:28)的版本 v8.5.49
web
Tomcat 的整體結構中有不少模塊,下圖列出咱們將要進行分析結構中的主要模塊。其中主要分析的是Service,Connector,Engine,Host,Context,Wrapper。爲避免圖層看着太亂,下圖中n
表明該組件可容許存在多個。apache
如上圖所描述的是:Server 是 tomcat 服務器,在 Server 中能夠存在多個服務 Service 。每一個服務中可有多個鏈接器和一個 Servlet 引擎 Engine,一個 Service 中多個鏈接器對應一個 Engine。 每一個 Engine 中,可存在多個域名,這裏可用虛擬主機的概念來表示 Host。每一個 Host 中能夠存在多個應用 Context。
Server,Service,Connector,Engine,Host,Context,Wrapper 它們之間的關係,除了Connector和Engine,它們是平行關係,其它的都是存在包含關係。同時,它們也都繼承了 Lifecycle 接口,該接口提供的是生命週期的管理,裏面包括:初始化(init),啓動(start),中止(stop),銷燬(destroy)。當它的父容器啓動時,會調用它子容器的啓動,中止也是同樣的。設計模式
上圖中,還能夠看到,Engine,Host,Context,Wrapper 都繼承自 Container。它有個backgroundProcess()
方法,後臺異步處理,因此繼承它後能夠方便的建立異步線程。
在 Tomcat7 中,有看到 Service 持有的是 Container,而不是 Engine。估計這也是爲何在當前版本中添加 Engine 方法名叫setContainer
。數組
Tomcat 源碼中有提供org.apache.catalina.Server
接口,對應的默認實現類爲org.apache.catalina.core.StandardServer
,接口裏面提供有以下圖方法。tomcat
上圖中能夠知道 Server 作的工做:對 Service,Address,Port,Catalina 以及全局命名資源的管理操做。
Server 在進行初始化的時候,會加載咱們 server.xml 中配置的數據。服務器
這裏對其中的 Service 操做的addService
向定義的服務集添加新服務進行分析:架構
// 保存服務的服務集 private Service services[] = new Service[0]; final PropertyChangeSupport support = new PropertyChangeSupport(this); @Override public void addService(Service service) { // 相互關聯 service.setServer(this); // 利用同步鎖,防止併發訪問 來源:https://ytao.top synchronized (servicesLock) { Service results[] = new Service[services.length + 1]; // copy 舊的服務到新的數組中 System.arraycopy(services, 0, results, 0, services.length); // 添加新的 service results[services.length] = service; services = results; // 若是當前 server 已經啓動,那麼當前添加的 service 就開始啓動 if (getState().isAvailable()) { try { service.start(); } catch (LifecycleException e) { // Ignore } } // 使用觀察者模式,當被監聽對象屬性值發生變化時通知監聽器,remove 是也會調用。 support.firePropertyChange("service", null, service); } }
源碼中能夠看到,向服務器中添加服務後,隨機會啓動服務,實則也服務啓動入口。併發
Service 的主要職責就是將 Connector 和 Engine 的組裝在一塊兒。二者分開的目的也就是使請求監聽和請求處理進行解耦,能擁有更好的擴展性。每一個 Service 都是相互獨立的,可是共享一個JVM和系統類庫。這裏提供了org.apache.catalina.Service
接口和默認實現類org.apache.catalina.coreStandardService
。app
在實現類 StandardService 中,主要分析setContainer
和addConnector
兩個方法。
private Engine engine = null; protected final MapperListener mapperListener = new MapperListener(this); @Override public void setContainer(Engine engine) { Engine oldEngine = this.engine; // 判斷當前 Service 是否有關聯 Engine if (oldEngine != null) { // 若是當前 Service 有關聯 Engine,就去掉當前關聯的 Engine oldEngine.setService(null); } // 若是當前新的 Engine 不爲空,那麼 Engine 關聯當前 Service,這裏是個雙向關聯 this.engine = engine; if (this.engine != null) { this.engine.setService(this); } // 若是當前 Service 啓動了,那麼就開始啓動當前新的 Engine if (getState().isAvailable()) { if (this.engine != null) { try { this.engine.start(); } catch (LifecycleException e) { log.error(sm.getString("standardService.engine.startFailed"), e); } } // 重啓 MapperListener ,獲取一個新的 Engine ,必定是當前入參的 Engine try { mapperListener.stop(); } catch (LifecycleException e) { log.error(sm.getString("standardService.mapperListener.stopFailed"), e); } try { mapperListener.start(); } catch (LifecycleException e) { log.error(sm.getString("standardService.mapperListener.startFailed"), e); } // 若是當前 Service 以前有 Engine 關聯,那麼中止以前的 Engine if (oldEngine != null) { try { oldEngine.stop(); } catch (LifecycleException e) { log.error(sm.getString("standardService.engine.stopFailed"), e); } } } // Report this property change to interested listeners support.firePropertyChange("container", oldEngine, this.engine); } /** * 實現方式和 StandardServer#addService 相似,不在細述 * 注意,Connector 這裏沒有像 Engine 同樣與 Service 實現雙向關聯 */ @Override public void addConnector(Connector connector) { synchronized (connectorsLock) { connector.setService(this); Connector results[] = new Connector[connectors.length + 1]; System.arraycopy(connectors, 0, results, 0, connectors.length); results[connectors.length] = connector; connectors = results; if (getState().isAvailable()) { try { connector.start(); } catch (LifecycleException e) { log.error(sm.getString( "standardService.connector.startFailed", connector), e); } } // Report this property change to interested listeners support.firePropertyChange("connector", null, connector); } }
Connector 主要用於接收請求,而後交給 Engine 處理請求,處理完後再給 Connector 去返回給客戶端。當前使用版本支持的協議有:HTTP,HHTP/2,AJP,NIO,NIO2,APR
主要的功能包括:
Connector 對應服務器 server.xml 中配置信息的例子:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
這裏經過配置監聽的端口號port
,指定處理協議protocol
,以及重定向地址redirectPort
。
協議處理類型經過實例化鏈接器時設置:
public Connector() { // 無參構造,下面 setProtocol 中默認使用HTTP/1.1 this(null); } public Connector(String protocol) { // 設置當前鏈接器協議處理類型 setProtocol(protocol); // 實例化協議處理器,並保存到當前 Connector 中 ProtocolHandler p = null; try { Class<?> clazz = Class.forName(protocolHandlerClassName); p = (ProtocolHandler) clazz.getConstructor().newInstance(); } catch (Exception e) { log.error(sm.getString( "coyoteConnector.protocolHandlerInstantiationFailed"), e); } finally { this.protocolHandler = p; } if (Globals.STRICT_SERVLET_COMPLIANCE) { uriCharset = StandardCharsets.ISO_8859_1; } else { uriCharset = StandardCharsets.UTF_8; } } /** * 這個設置再 tomcat9 中被移除,改成必配項 */ public void setProtocol(String protocol) { boolean aprConnector = AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseAprConnector(); // 這裏指定了默認協議和 HTTP/1.1 同樣 if ("HTTP/1.1".equals(protocol) || protocol == null) { if (aprConnector) { setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol"); } else { setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol"); } } else if ("AJP/1.3".equals(protocol)) { if (aprConnector) { setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol"); } else { setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol"); } } else { // 最後若是不是經過指定 HTTP/1.1,AJP/1.3 類型的協議,就經過類名實例化一個協議處理器 setProtocolHandlerClassName(protocol); } }
ProtocolHandler 是一個協議處理器,針對不一樣的請求,提供不一樣實現。實現類 AbstractProtocol 在初始化時,會在最後調用一個抽象類 AbstractEndpoint 初始化來啓動線程來監聽服務器端口,當接收到請求後,調用 Processor 讀取請求,而後交給 Engine 處理請求。
Engine 對應的是,org.apache.catalina.Engine
接口和org.apache.catalina.core.StandardEngine
默認實現類。
Engine 的功能也比較簡單,處理容器關係的關聯。
可是實現類中的addChild()
不是指的子 Engine,而是隻能是 Host。同時沒有父容器,setParent
是不容許操做設置的。
@Override public void addChild(Container child) { // 添加的子容器必須是 Host if (!(child instanceof Host)) throw new IllegalArgumentException (sm.getString("standardEngine.notHost")); super.addChild(child); } @Override public void setParent(Container container) { throw new IllegalArgumentException (sm.getString("standardEngine.notParent")); }
server.xml 能夠配置咱們的數據:
<!-- 配置默認Host,及jvmRoute --> <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
Host 表示一個虛擬主機。應爲咱們的服務器可設置多個域名,好比 demo.ytao.top,dev.ytao.top。那麼咱們就要設置兩個不一樣 Host 來處理不一樣域名的請求。當過來的請求域名爲 demo.ytao.top 時,那麼它就會去找該域名 Host 下的 Context。
因此咱們的 server.xml 配置文件也提供該配置:
<!-- name 設置的時虛擬主機域名 --> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
到 Context 這裏來,就擁有 Servlet 的運行環境,Engine,Host都是主要維護容器關係,不具有運行環境。
咱們暫且可將 Context 理解爲一個應用,例如咱們在根目錄下有 ytao-demo-1 和 ytao-demo-2 兩個應用,那麼這裏就是有兩個 Context。
這裏主要介紹的addChild
方法,該添加的子容器是 Wrapper:
@Override public void addChild(Container child) { // Global JspServlet Wrapper oldJspServlet = null; // 這裏添加的子容器只能時 Wrapper if (!(child instanceof Wrapper)) { throw new IllegalArgumentException (sm.getString("standardContext.notWrapper")); } // 判斷子容器 Wrapper 是否爲 JspServlet boolean isJspServlet = "jsp".equals(child.getName()); // Allow webapp to override JspServlet inherited from global web.xml. if (isJspServlet) { oldJspServlet = (Wrapper) findChild("jsp"); if (oldJspServlet != null) { removeChild(oldJspServlet); } } super.addChild(child); // 將servlet映射添加到Context組件 if (isJspServlet && oldJspServlet != null) { /* * The webapp-specific JspServlet inherits all the mappings * specified in the global web.xml, and may add additional ones. */ String[] jspMappings = oldJspServlet.findMappings(); for (int i=0; jspMappings!=null && i<jspMappings.length; i++) { addServletMappingDecoded(jspMappings[i], child.getName()); } } }
這裏也就是每一個應用中的 Servlet 管理中心。
Wrapper 是一個 Servlet 的管理中心,它擁有 Servlet 的整個生命週期,它是沒有子容器的,由於它本身就是最底層的容器了。
這裏主要對 Servlet 加載的分析:
public synchronized Servlet loadServlet() throws ServletException { // 若是已經實例化或者用實例化池,就直接返回 if (!singleThreadModel && (instance != null)) return instance; PrintStream out = System.out; if (swallowOutput) { SystemLogHandler.startCapture(); } Servlet servlet; try { long t1=System.currentTimeMillis(); // 若是 servlet 類名爲空,直接拋出 Servlet 異常 if (servletClass == null) { unavailable(null); throw new ServletException (sm.getString("standardWrapper.notClass", getName())); } // 從 Context 中獲取 Servlet InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager(); try { servlet = (Servlet) instanceManager.newInstance(servletClass); } catch (ClassCastException e) { unavailable(null); // Restore the context ClassLoader throw new ServletException (sm.getString("standardWrapper.notServlet", servletClass), e); } catch (Throwable e) { e = ExceptionUtils.unwrapInvocationTargetException(e); ExceptionUtils.handleThrowable(e); unavailable(null); // Added extra log statement for Bugzilla 36630: // https://bz.apache.org/bugzilla/show_bug.cgi?id=36630 if(log.isDebugEnabled()) { log.debug(sm.getString("standardWrapper.instantiate", servletClass), e); } // Restore the context ClassLoader throw new ServletException (sm.getString("standardWrapper.instantiate", servletClass), e); } // 加載聲明瞭 MultipartConfig 註解的信息 if (multipartConfigElement == null) { MultipartConfig annotation = servlet.getClass().getAnnotation(MultipartConfig.class); if (annotation != null) { multipartConfigElement = new MultipartConfigElement(annotation); } } // 對 servlet 類型進行檢查 if (servlet instanceof ContainerServlet) { ((ContainerServlet) servlet).setWrapper(this); } classLoadTime=(int) (System.currentTimeMillis() -t1); if (servlet instanceof SingleThreadModel) { if (instancePool == null) { instancePool = new Stack<>(); } singleThreadModel = true; } // 初始化 servlet initServlet(servlet); fireContainerEvent("load", this); loadTime=System.currentTimeMillis() -t1; } finally { if (swallowOutput) { String log = SystemLogHandler.stopCapture(); if (log != null && log.length() > 0) { if (getServletContext() != null) { getServletContext().log(log); } else { out.println(log); } } } } return servlet; }
這裏加載 Servlet,若是該 Servlet 沒有被實例化過,那麼必定要加載一個。
到目前爲止,大體介紹了 Tomcat8 的主要組件,對 Tomcat 的總體架構也有個大體瞭解了,Tomcat 源碼進行重構後,可讀性確實要好不少,建議你們能夠去嘗試分析下,裏面的使用的一些設計模式,咱們在實際編碼過程當中,仍是有必定的借鑑意義。
我的博客: https://ytao.top
個人公衆號 ytao