一、Tomcat目錄
bin:存儲Tomcat相關可執行腳本,如啓動和關閉Tomcat的命令start.sh和shutdown.sh
conf:存儲Tomcat相關的配置文件
- server.xml:Tomcat的服務配置,Tomcat啓動就代表一個Server,需要配置Server下的Service、Connector、Engine、Hosts等組件
- web.xml:Tomcat的Servlet規範標準的配置文件,Tomcat本質上就是一個Servlet,主要配置了兩個Servlet,一個是DefaultServlet處理所有請求,一個是JspServlet專門負責處理.jsp的請求
- tomcat-users.xml:Tomcat相關的用戶和角色配置,常規情況下用不到
- context.xml:所有Host的默認配置信息
logs:存儲Tomcat運行時的日誌文件
work:存儲jsp編譯後產生的.class文件
webapps:存儲Tomcat主要web發佈目錄
lib:存儲Tomcat程序依賴的jar包
二、Tomcat的組件及整體架構
2.1、Tomcat的組件
Server:Server表示服務器,每啓動一個Tomcat實例就相當於啓動一個JVM,也就代表一個Server;
Service:Service表示服務器的一個服務,默認服務名稱是Catalina,每個Service都需要關聯一個引擎Engine和一組連接器Connector;
Connector:連接器,用於監聽指定端口,接收指定通信協議的請求並將請求轉發給關聯的引擎Engine處理,然後把處理結果返回給客戶端;
Engine:Servlet引擎,相當於一個Servlet的實例,Engine需要將Connector轉發過來的請求根據請求host將請求交給對應的Host來處理;
Host:表示一個主機,用於處理Engine分配的請求
Context:表示一個Web應用程序,根據Host處理的請求路徑匹配
2.2、Tomcat組件配置
server配置
標籤爲<Server>,主要屬性有port和shutdown表示監聽指定端口接收指定的shutdown命令用於關閉Tomcat服務器,配置如下:
<Server port="8005" shutdown="SHUTDOWN">
則表示Tomcat服務會監聽8005端口,並接收shutdown命令,用於關閉Tomcat服務器。如果同一個物理機器需要啓動多個Tomcat實例那麼就需要定義不同的port。<Server>標籤可以配置屬性如下
port:監聽的端口
shutdown:接收用於關閉Tomcat的命令,默認是SHUTDOWN
className:實現Server的類名,默認是org.apache.catalina.core.StandardServer
Service配置
標籤爲<Service>, 主要屬性有name和className,默認name爲Catalina,默認className爲org.apache.core.StandardServic
每個<Service>需要關聯一個<Engine>和至少一個<Connector>,如配置如下
<Service name="Catalina">
Connector配置
標籤爲<Connector>,Tomcat的連接器類型有多個,分別有HTTP連接器、SSL連接器、AJP1.3連接器、proxy連接器,不同連接器使用場景不同配置的屬性也不同,連接器需要關聯一個引擎Engine,但是一個Engine可以關聯多個連接器Connector。配置如下:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" maxPostSize="-1" URIEncoding="UTF-8" maxThreads="200" redirectPort="8443" />
表示這個連接器監聽8080端口和HTTP1.1協議,鏈接超時時間爲20秒,最大併發線程爲200各個線程
Connector標籤主要參數如下:
參數 | 說明 | 取值 |
port | 連接器監聽端口號 | 默認8080 |
protocol | 協議類型 |
可選BIO、NIO、NIO2、APR四種類型,取值分別爲 BIO:HTTP1.1 NIO:org.apache.coyote.http11.Http11NioProtocol NIO2:org.apache.coyote.http11.Http11Nio2Protocol APR:org.apache.coyote.http11.Http11AprProtocol |
connectionTimeout | 連接超時時間,請求已經被接收,但是還未被處理,也就是等待處理的超時時間 | 單位毫秒,默認值爲60000 |
maxThreads | 處理請求的最大線程數 | 默認值爲200 |
minSpareThreads | 處理請求的最小線程數 | 默認值爲10 |
acceptCount | 等待隊列的最大數,表示等待處理的請求等待隊列 | 默認值爲100,通常可以設置爲maxThreads大小 |
maxConnections | 在任何給定的時間內,服務器將接受和處理的最大連接數,超過最大連接數就不在處理連接 | NIO和NIO2默認值爲10000,APR默認值爲8192 |
maxHttpHeaderSize | 請求和響應的HTTP請求頭最大大小 | 單位爲字節,默認是8192也就是8K |
tcpNoDelay | 配置TCP的TCP_NO_DELAY屬性,是否禁用Nagle算法 | 默認爲true |
compression | 是否啓用gzip壓縮 | 默認爲關閉狀態off,可選值爲on壓縮文本數據,force強制壓縮所有數據 |
compressionMinSize | gzip壓縮的最小值,也就是數據大小超過該值時compression=on的配置纔有效 | 默認是2048也就是2K |
URIEncoding | 設置URL編碼字符集 | 可以設置爲UTF-8 |
executor | 處理請求的線程池,請求會放到線程池中執行 | 需要配置線程池,然後將線程池名稱賦值給executor屬性 |
redirectPort | 重定向端口,如果協議爲HTTP,當接收HTTPS請求時轉發到該端口 | 默認是443 |
Engine配置
標籤爲<Engine>表示一個處理Servlet引擎,屬性有name和defalutHost,配置如下
<Engine name="Catalina" defaultHost="localhost">
defaultHost:Tomcat支持基於FQDN的虛擬主機,這些虛擬主機可以通過在Engine容器中定義多個不同的Host組件來實現;但如果此引擎的連接器收到一個發往非非明確定義虛擬主機的請求時則需要將此請求發往一個默認的虛擬主機進行處理,因此,在Engine中定義的多個虛擬主機的主機名稱中至少要有一個跟defaultHost定義的主機名稱同名;
name:Engine組件的名稱,用於日誌和錯誤信息記錄時區別不同的引擎
Host配置
標籤爲<Host>位於Engine容器中用於接收請求並進行相應處理的主機或虛擬主機,配置如下:
<Host name="local" appBase="webapps" unpackWARS="true" autoDeploy="true">
常有屬性說明
appBase:webapp目錄,即存放非歸檔的web應用程序的目錄或歸檔後的WAR文件的目錄路徑;可以使用基於$CATALINA_HOME的相對路徑;
unpackWARS:在啓用此webapps時是否對WAR格式的歸檔文件先進行展開,也就是是否解壓WAR包;默認爲true;
autoDeploy:在Tomcat處於運行狀態時放置於appBase目錄中的應用程序文件是否自動進行deploy;默認爲true;
Context配置
標籤爲<Context>,一個Context代表一個web應用程序,配置如下:
<Context path="/order" docBase="/web/order" >
其中path表示相當於web服務器根路徑而言的URL,如果沒有配置則爲webapp根路徑;docBase表示對應的web應用程序存放位置,可以是絕對路徑,也可以是相對路徑,相對路徑是在Host的appBase之下的路徑。
2.3、Tomcat整體架構
Tomcat整體架構如上圖示,Tomcat程序啓動代表一個Tomcat Server,Server下包含多個Service,每個Service代表一個服務,Service需要包含一個Engine和多個Connector,其中Engine和Connector關聯,Connector負責監聽端口接收客戶端請求,並將請求轉發給Engine,Engine是處理請求引擎專門用於處理請求,Engine下面包含多個Host,Engine會根據請求的host信息匹配對應的Host,將請求交給Host,Host再根據具體的請求路徑匹配對應的Context,Context再處理對應的具體請求。Context就代表具體的Web應用程序。
2.4、Tomcat的請求處理流程
1、客戶端發送HTTP請求,如路徑爲 http://localhost:8080/order/createOrder;
2、監聽8080端口和HTTP協議的Connector接收到請求,並將請求交給關聯的Engine;
3、Engine獲取請求的host爲localhost,在所有的虛擬主機Host中進行匹配,找到匹配的Host,並將請求交給對應的Host,如果匹配不到就交給默認的localhost的Host;
4、Host根據請求路徑/order匹配所有Context的path,根據Context的path找到對應的Context,如果匹配不到就將請求交給path=""的Context處理;
5、Context根據路徑集合找到對應路徑的Servlet,構造HttpServletRequest和HttpServletResponse對象,調用對應Servlet的處理方法,根據請求類型分別調用doGet、doPost、doPut、doDelete等方法;
6、Servlet處理請求並將結果HttpServletResponse返回給Host;
7、Host將結果HttpServletResponse返回給Engine;
8、Engine將結果HttpServletResponse返回給Connector;
9、Connector將結果HttpServletResponse返回給客戶端;
三、Tomcat的實現原理
3.1、Tomcat啓動實現原理
Tomcat文件夾bin目錄下有很多腳本文件,和啓動有關的主要是startup.sh、catalina.sh和setclasspath.sh三個文件
Tomcat啓動執行的腳本爲startup.sh(window系統爲startup.bat),該腳本的作用是找到catalina.sh並且執行;而catalina.sh的主要作用就是配置環境變量,然後找到setclasspath.sh並且執行,另外catalina.sh還可以設置JAVA_OPTS來配置應用程序的啓動參數,
然後定義Tomcat的啓動類爲MAINCLASS=org.apache.catalina.startup.Bootstrap並執行Bootstrap類的main方法啓動Tomcat
雖然總結起來流程比較簡單,但是實際上這三個腳本文件的內容還是有很多的,有興趣可以自行查看研究。
從啓動文件可以得知Tomcat啓動實際就是執行了org.apache.catalina.startup.Bootstrap的main方法,所以可以從這個main方法入手來了解Tomcat是如何啓動的,Bootstrap的main方法源碼如下:
1 public static void main(String args[]) { 2 /** 同步保證同一時間只啓動一次*/ 3 synchronized (daemonLock) { 4 if (daemon == null) { 5 Bootstrap bootstrap = new Bootstrap(); 6 try { 7 /** 1.執行初始化方法 */ 8 bootstrap.init(); 9 } catch (Throwable t) { 10 handleThrowable(t); 11 t.printStackTrace(); 12 return; 13 } 14 daemon = bootstrap; 15 } else { 16 Thread.currentThread().setContextClassLoader(daemon.catalinaLoader); 17 } 18 } 19 20 try { 21 /** 2.從啓動參數中獲取命令 */ 22 String command = "start"; 23 if (args.length > 0) { 24 command = args[args.length - 1]; 25 } 26 27 if (command.equals("startd")) {//啓動 28 args[args.length - 1] = "start"; 29 daemon.load(args); 30 daemon.start(); 31 } else if (command.equals("stopd")) {//關閉 32 args[args.length - 1] = "stop"; 33 daemon.stop(); 34 } else if (command.equals("start")) {//啓動 35 daemon.setAwait(true); 36 daemon.load(args); 37 daemon.start(); 38 if (null == daemon.getServer()) { 39 System.exit(1); 40 } 41 } else if (command.equals("stop")) {//關閉 42 daemon.stopServer(args); 43 } else if (command.equals("configtest")) {//配置 44 daemon.load(args); 45 if (null == daemon.getServer()) { 46 System.exit(1); 47 } 48 System.exit(0); 49 } 50 } catch (Throwable t) { 51 System.exit(1); 52 } 53 }
核心邏輯比較簡單流程也比較清晰,首先在同步代碼塊中調用Bootstrap對象的init方法進行初始化,然後根據啓動參數的命令執行對應的方法,分別調用load、start、stop或stopServer等方法。
實際上Tomcat啓動流程主要分成兩大步驟,第一步是核心組件的初始化,第二步是啓動,而Catalina的load方法就是初始化過程,Catalina的start方法就是真正的啓動過程。
3.1.1、Catalina初始化源碼解析
Bootstrap的init方法源碼如下:
1 public void init() throws Exception { 2 /** 1.初始化類加載器 */ 3 initClassLoaders(); 4 5 Thread.currentThread().setContextClassLoader(catalinaLoader); 6 7 SecurityClassLoad.securityClassLoad(catalinaLoader); 8 9 Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina"); 10 Object startupInstance = startupClass.getConstructor().newInstance(); 11 12 String methodName = "setParentClassLoader"; 13 Class<?> paramTypes[] = new Class[1]; 14 paramTypes[0] = Class.forName("java.lang.ClassLoader"); 15 Object paramValues[] = new Object[1]; 16 paramValues[0] = sharedLoader; 17 Method method = startupInstance.getClass().getMethod(methodName, paramTypes); 18 /** 2.通過反射調用Catalina的setParentClassLoader方法 */ 19 method.invoke(startupInstance, paramValues); 20 21 catalinaDaemon = startupInstance; 22 }
init方法主要是初始化了類加載器,然後通過反射調用了Catalina的多setParentClassLoader方法。
而load、start、stop和stopServer等方法的實現和init方法一樣,實際就是通過反射調用了Catalina類的對應方法,如Bootstrap的start方法源碼如下:
1 public void start() throws Exception { 2 if (catalinaDaemon == null) { 3 init(); 4 } 5 /** 通過反射直接調用Catalina對象的start方法 */ 6 Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null); 7 method.invoke(catalinaDaemon, (Object [])null); 8 }
Catalina的load方法主要是完成了初始化工作,主要是初始化了代表整個Tomcat的Server對象,Catalina的load方法核心邏輯如下:
1 public void load() { 2 /** 1.初始化目錄 */ 3 initDirs(); 4 initNaming(); 5 ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile())); 6 /** 2.讀取配置文件 */ 7 File file = configFile(); 8 /** 3.創建Digester對象,用於xml文件解析 */ 9 Digester digester = createStartDigester(); 10 11 /** 4.通過Digester解析server.xml文件,分別解析Server、Service、Connector、Engine、Host、Context等標籤 */ 12 try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) { 13 InputStream inputStream = resource.getInputStream(); 14 InputSource inputSource = new InputSource(resource.getURI().toURL().toString()); 15 inputSource.setByteStream(inputStream); 16 digester.push(this); 17 digester.parse(inputSource); 18 } catch (Exception e) { 19 } 20 21 getServer().setCatalina(this); 22 getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile()); 23 getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile()); 24 25 initStreams(); 26 try { 27 /** 5.初始化Server */ 28 getServer().init(); 29 } catch (LifecycleException e) { 30 } 31 }
主要是通過Digester解析server.xml配置文件,依次讀取並解析<Server>、<Service>、<Connector>、<Engine>、<Host>、<Context>等標籤,分別初始化對應的實例StandardServer、StandardService、Connector、StandardEngine、Host、Context等對象。
然後調用Server的實例StandardServer對象的init方法進行初始化,StandardService的初始化方法執行的是父類的LifecycleBase的init方法,代碼如下:
1 public final synchronized void init() throws LifecycleException { 2 if (!state.equals(LifecycleState.NEW)) { 3 invalidTransition(Lifecycle.BEFORE_INIT_EVENT); 4 } 5 6 try { 7 /** 1.設置生命週期狀態,並拋出開始初始化事件 */ 8 setStateInternal(LifecycleState.INITIALIZING, null, false); 9 /** 2.執行初始化操作 */ 10 initInternal(); 11 /** 3.設置生命週期狀態,並拋出完成初始化事件 */ 12 setStateInternal(LifecycleState.INITIALIZED, null, false); 13 } catch (Throwable t) { 14 handleSubClassException(t, "lifecycleBase.initFail", toString()); 15 } 16 }
其中initInternal方法實際是調用子類也就是StandardServer本身的initInternal方法,該方法的核心邏輯是依次遍歷所有Service,然後依次調用Service的init方法,代碼如下:
1 protected void initInternal() throws LifecycleException { 2 3 super.initInternal(); 4 5 // Initialize utility executor 6 reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads)); 7 register(utilityExecutor, "type=UtilityExecutor"); 8 onameStringCache = register(new StringCache(), "type=StringCache"); 9 MBeanFactory factory = new MBeanFactory(); 10 factory.setContainer(this); 11 onameMBeanFactory = register(factory, "type=MBeanFactory"); 12 globalNamingResources.init(); 13 if (getCatalina() != null) { 14 ClassLoader cl = getCatalina().getParentClassLoader(); 15 // Walk the class loader hierarchy. Stop at the system class loader. 16 // This will add the shared (if present) and common class loaders 17 while (cl != null && cl != ClassLoader.getSystemClassLoader()) { 18 if (cl instanceof URLClassLoader) { 19 URL[] urls = ((URLClassLoader) cl).getURLs(); 20 for (URL url : urls) { 21 if (url.getProtocol().equals("file")) { 22 try { 23 File f = new File (url.toURI()); 24 if (f.isFile() && 25 f.getName().endsWith(".jar")) { 26 ExtensionValidator.addSystemResource(f); 27 } 28 } catch (URISyntaxException e) { 29 // Ignore 30 } catch (IOException e) { 31 // Ignore 32 } 33 } 34 } 35 } 36 cl = cl.getParent(); 37 } 38 } 39 /** 初始化Server下所有的Service,調用對應Service的init方法 */ 40 for (int i = 0; i < services.length; i++) { 41 services[i].init(); 42 } 43 }
Service的實例是StandardService對象,初始化方法和StandardServer初始化方法邏輯類似,也是執行了本身的initInternal方法,邏輯就是初始化調用了對應的Engine的init方法和所有Connector的init方法,代碼如下:
1 protected void initInternal() throws LifecycleException { 2 super.initInternal(); 3 if (engine != null) { 4 /** 1.初始化Engine */ 5 engine.init(); 6 } 7 for (Executor executor : findExecutors()) { 8 if (executor instanceof JmxEnabled) { 9 ((JmxEnabled) executor).setDomain(getDomain()); 10 } 11 /** 2.初始化線程池*/ 12 executor.init(); 13 } 14 mapperListener.init(); 15 /** 3.初始化所有Connector */ 16 synchronized (connectorsLock) { 17 for (Connector connector : connectors) { 18 connector.init(); 19 } 20 } 21 }
其中Engine的初始化方法主要是初始化Realm,Connector初始化主要是初始化ProtocolHandler對象,ProtocolHandler是具體的通信協議處理器,Connector初始化比較重要,相當於初始化後就開始監聽指定協議的指定端口了。
Connector的ProcotolHandler初始化需要根據<Connector>標籤的屬性配置來決定使用哪個實現,如protocol=HTTP/1.1那麼就是Http11NioProtocol,如果protocol=AJP/1.1那麼就是AjpNioProtocol,如果是APR那麼就是對應協議的PRO處理器,
默認是Http11NioProtocol,Connector對象創建時會通過反射創建對應協議處理器。Connector初始化initInternal方法源碼如下:
protected void initInternal() throws LifecycleException { super.initInternal(); if (protocolHandler == null) { throw new LifecycleException( sm.getString("coyoteConnector.protocolHandlerInstantiationFailed")); } /** 初始化Coyote適配器 */ adapter = new CoyoteAdapter(this); protocolHandler.setAdapter(adapter); if (service != null) { /** 設置ProtocolHandler處理線程池 */ protocolHandler.setUtilityExecutor(service.getServer().getUtilityExecutor()); } // Make sure parseBodyMethodsSet has a default if (null == parseBodyMethodsSet) { setParseBodyMethods(getParseBodyMethods()); } if (protocolHandler.isAprRequired() && !AprLifecycleListener.isInstanceCreated()) { throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprListener", getProtocolHandlerClassName())); } if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) { throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprLibrary", getProtocolHandlerClassName())); } if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() && protocolHandler instanceof AbstractHttp11JsseProtocol) { AbstractHttp11JsseProtocol<?> jsseProtocolHandler = (AbstractHttp11JsseProtocol<?>) protocolHandler; if (jsseProtocolHandler.isSSLEnabled() && jsseProtocolHandler.getSslImplementationName() == null) { // OpenSSL is compatible with the JSSE configuration, so use it if APR is available jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName()); } } try { /** 初始化ProtocolHandler */ protocolHandler.init(); } catch (Exception e) { throw new LifecycleException( sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e); } }
可以看出Connector初始化的重點是調用了ProtocolHandler的init方法進行初始化,以Http11NioProtocol爲例,init方法實際是執行的父類AbstractProtocol的init方法,核心邏輯是初始化NioEndpoint對象,並調用NioEndpoint的init方法進行初始化。
NioEndpoint的init方法執行的是父類AbstractEndpoint的init方法,源碼如下:
1 public final void init() throws Exception { 2 // 初始化時綁定端口 3 if (bindOnInit) { 4 /** 綁定端口號 */ 5 bindWithCleanup(); 6 bindState = BindState.BOUND_ON_INIT; 7 } 8 if (this.domain != null) { 9 // Register endpoint (as ThreadPool - historical name) 10 oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\""); 11 Registry.getRegistry(null, null).registerComponent(this, oname, null); 12 13 ObjectName socketPropertiesOname = new ObjectName(domain + 14 ":type=ThreadPool,name=\"" + getName() + "\",subType=SocketProperties"); 15 socketProperties.setObjectName(socketPropertiesOname); 16 Registry.getRegistry(null, null).registerComponent(socketProperties, socketPropertiesOname, null); 17 18 for (SSLHostConfig sslHostConfig : findSslHostConfigs()) { 19 registerJmx(sslHostConfig); 20 } 21 } 22 } 23 24 private void bindWithCleanup() throws Exception { 25 try { 26 /** 綁定端口號 */ 27 bind(); 28 } catch (Throwable t) { 29 ExceptionUtils.handleThrowable(t); 30 /** 異常情況解綁端口 */ 31 unbind(); 32 throw t; 33 } 34 }
實際上最終還是調用了NioEndpoint的bind方法進行端口綁定,源碼如下:
public void bind() throws Exception { /** 初始化ServerSocket,綁定端口 */ initServerSocket(); setStopLatch(new CountDownLatch(1)); /** 初始化SSL*/ initialiseSsl(); /** 打開Selector監聽端口*/ selectorPool.open(getName()); }
實際就是初始化ServerSocket監聽端口號,並初始化了多路複用選擇器Selector
3.1.2、Catalina啓動源碼解析
Catalina的start方法源碼核心邏輯如下:
public void start() { try { /** 調用StandardServer的start方法 * 實際是執行父類Lifecycle的start方法 */ getServer().start(); } catch (LifecycleException e) { try { getServer().destroy(); } catch (LifecycleException e1) { } return; } } /** Lifecycle的start方法*/ public final synchronized void start() throws LifecycleException { /** 狀態檢查*/ if (state.equals(LifecycleState.NEW)) { init(); } else if (state.equals(LifecycleState.FAILED)) { stop(); } else if (!state.equals(LifecycleState.INITIALIZED) && !state.equals(LifecycleState.STOPPED)) { invalidTransition(Lifecycle.BEFORE_START_EVENT); } try { /** 設置狀態*/ setStateInternal(LifecycleState.STARTING_PREP, null, false); /** 調用子類的startInternal方法 */ startInternal(); if (state.equals(LifecycleState.FAILED)) { stop(); } else if (!state.equals(LifecycleState.STARTING)) { invalidTransition(Lifecycle.AFTER_START_EVENT); } else { setStateInternal(LifecycleState.STARTED, null, false); } } catch (Throwable t) { handleSubClassException(t, "lifecycleBase.startFail", toString()); } }
直接調用getServer().start()調用StandardServer的start方法,而StandardServer是LifecycleBase的子類,所以執行的是LifecycleBase的start方法,最終調用子類的startInternal方法啓動,也就是執行了StandardServer的startInternal方法,源碼如下:
1 protected void startInternal() throws LifecycleException { 2 /** 1.發送生命週期變化事件*/ 3 fireLifecycleEvent(CONFIGURE_START_EVENT, null); 4 /** 2.設置狀態爲啓動狀態*/ 5 setState(LifecycleState.STARTING); 6 globalNamingResources.start(); 7 /** 3.遍歷啓動所有service*/ 8 synchronized (servicesLock) { 9 for (int i = 0; i < services.length; i++) { 10 services[i].start(); 11 } 12 } 13 14 if (periodicEventDelay > 0) { 15 monitorFuture = getUtilityExecutor().scheduleWithFixedDelay( 16 new Runnable() { 17 @Override 18 public void run() { 19 startPeriodicLifecycleEvent(); 20 } 21 }, 0, 60, TimeUnit.SECONDS); 22 } 23 }
Server的start方法實際就是調用所有Service的start方法,Service的start方法實際執行的是startInternal方法,源碼如下:
/** StandardService 啓動方法 */ protected void startInternal() throws LifecycleException { /** 1.設置狀態爲啓動中 */ setState(LifecycleState.STARTING); /** 2.啓動Engine */ if (engine != null) { synchronized (engine) { engine.start(); } } /** 3.啓動線程池 */ synchronized (executors) { for (Executor executor: executors) { executor.start(); } } /** 4.啓動MapperListener */ mapperListener.start(); /** 5.啓動所有Connector,監聽端口 */ synchronized (connectorsLock) { for (Connector connector: connectors) { // If it has already failed, don't try and start it if (connector.getState() != LifecycleState.FAILED) { connector.start(); } } } }
核心邏輯就是啓動了Service對應的Engine和所有的Connector,分別調用各自的start方法,實際就是分別調用StandardEngine和Connector的startInternal方法。
3.1.3、Engine啓動
StandardEngine的startInternal實際調用的是父類ContainerBase的startInternal,Container表示容器,Engine、Host、Context都是容器的實現。ContainerBase的startInternal方法源碼核心邏輯如下:
1 protected synchronized void startInternal() throws LifecycleException { 2 3 //。。。。。。 4 5 /** 1.查詢所有子容器*/ 6 Container children[] = findChildren(); 7 List<Future<Void>> results = new ArrayList<>(); 8 for (int i = 0; i < children.length; i++) { 9 /** 2.依次啓動子容器 */ 10 results.add(startStopExecutor.submit(new StartChild(children[i]))); 11 } 12 13 MultiThrowable multiThrowable = null; 14 15 for (Future<Void> result : results) { 16 try { 17 result.get(); 18 } catch (Throwable e) { 19 log.error(sm.getString("containerBase.threadedStartFailed"), e); 20 if (multiThrowable == null) { 21 multiThrowable = new MultiThrowable(); 22 } 23 multiThrowable.add(e); 24 } 25 26 } 27 /** 3.設置狀態*/ 28 setState(LifecycleState.STARTING); 29 if (backgroundProcessorDelay > 0) { 30 monitorFuture = Container.getService(ContainerBase.this).getServer() 31 .getUtilityExecutor().scheduleWithFixedDelay( 32 new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS); 33 } 34 }
首先是找到所有子容器,然後分別啓動子容器,Engine的子容器就是Host,所以對於Engine而言啓動子容器實際就是啓動了StandardHost,而StandardHost的startInternal實際就是調用了所有的StandardContext的start方法。
StandardContext的startInternal方法源碼比較多,核心邏輯如下:
1 protected synchronized void startInternal() throws LifecycleException { 2 3 setConfigured(false); 4 boolean ok = true; 5 6 /** 1.創建臨時工作目錄 */ 7 postWorkDirectory(); 8 /** 2.初始化Context使用的WebResourceRoot並啓動, WebResourceRoot維護Web應用所有資源文件*/ 9 if (getResources() == null) { 10 try { 11 setResources(new StandardRoot(this)); 12 } catch (IllegalArgumentException e) { 13 ok = false; 14 } 15 } 16 if (ok) { 17 resourcesStart(); 18 } 19 20 /** 3.創建web應用類加載器 */ 21 if (getLoader() == null) { 22 WebappLoader webappLoader = new WebappLoader(getParentClassLoader()); 23 webappLoader.setDelegate(getDelegate()); 24 setLoader(webappLoader); 25 } 26 27 /** 4.創建Cookie處理器 */ 28 if (cookieProcessor == null) { 29 cookieProcessor = new Rfc6265CookieProcessor(); 30 } 31 32 /** 5.初始化字符集 */ 33 getCharsetMapper(); 34 35 /** 6.依賴檢查 */ 36 boolean dependencyCheck = true; 37 try { 38 dependencyCheck = ExtensionValidator.validateApplication 39 (getResources(), this); 40 } catch (IOException ioe) { 41 log.error(sm.getString("standardContext.extensionValidationError"), ioe); 42 dependencyCheck = false; 43 } 44 45 if (!dependencyCheck) { 46 ok = false; 47 } 48 49 /** 7.註冊NamingContextListener */ 50 String useNamingProperty = System.getProperty("catalina.useNaming"); 51 if ((useNamingProperty != null) 52 && (useNamingProperty.equals("false"))) { 53 useNaming = false; 54 } 55 56 if (ok && isUseNaming()) { 57 if (getNamingContextListener() == null) { 58 NamingContextListener ncl = new NamingContextListener(); 59 ncl.setName(getNamingContextName()); 60 ncl.setExceptionOnFailedWrite(getJndiExceptionOnFailedWrite()); 61 addLifecycleListener(ncl); 62 setNamingContextListener(ncl); 63 } 64 } 65 66 /** 8.綁定線程 */ 67 ClassLoader oldCCL = bindThread(); 68 69 try { 70 if (ok) { 71 /** 9.啓動web應用類加載器*/ 72 Loader loader = getLoader(); 73 if (loader instanceof Lifecycle) { 74 ((Lifecycle) loader).start(); 75 } 76 setClassLoaderProperty("clearReferencesRmiTargets", 77 getClearReferencesRmiTargets()); 78 setClassLoaderProperty("clearReferencesStopThreads", 79 getClearReferencesStopThreads()); 80 setClassLoaderProperty("clearReferencesStopTimerThreads", 81 getClearReferencesStopTimerThreads()); 82 setClassLoaderProperty("clearReferencesHttpClientKeepAliveThread", 83 getClearReferencesHttpClientKeepAliveThread()); 84 setClassLoaderProperty("clearReferencesObjectStreamClassCaches", 85 getClearReferencesObjectStreamClassCaches()); 86 setClassLoaderProperty("clearReferencesObjectStreamClassCaches", 87 getClearReferencesObjectStreamClassCaches()); 88 setClassLoaderProperty("clearReferencesThreadLocals", 89 getClearReferencesThreadLocals()); 90 91 unbindThread(oldCCL); 92 oldCCL = bindThread(); 93 logger = null; 94 getLogger(); 95 /** 10.啓動安全組件Realm */ 96 Realm realm = getRealmInternal(); 97 if(null != realm) { 98 if (realm instanceof Lifecycle) { 99 ((Lifecycle) realm).start(); 100 } 101 CredentialHandler safeHandler = new CredentialHandler() { 102 @Override 103 public boolean matches(String inputCredentials, String storedCredentials) { 104 return getRealmInternal().getCredentialHandler().matches(inputCredentials, storedCredentials); 105 } 106 107 @Override 108 public String mutate(String inputCredentials) { 109 return getRealmInternal().getCredentialHandler().mutate(inputCredentials); 110 } 111 }; 112 context.setAttribute(Globals.CREDENTIAL_HANDLER, safeHandler); 113 } 114 115 /** 11.發佈啓動事件CONFIGURE_START_EVENT */ 116 fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null); 117 118 /** 12.查詢子節點並啓動,實際就是Wrapper,負責管理Servlet*/ 119 for (Container child : findChildren()) { 120 if (!child.getState().isAvailable()) { 121 child.start(); 122 } 123 } 124 125 /** 13.啓動Context的pipeline */ 126 if (pipeline instanceof Lifecycle) { 127 ((Lifecycle) pipeline).start(); 128 } 129 130 /** 14.創建會話管理器 */ 131 Manager contextManager = null; 132 Manager manager = getManager(); 133 if (manager == null) { 134 if (log.isDebugEnabled()) { 135 log.debug(sm.getString("standardContext.cluster.noManager", 136 Boolean.valueOf((getCluster() != null)), 137 Boolean.valueOf(distributable))); 138 } 139 if ((getCluster() != null) && distributable) { 140 try { 141 contextManager = getCluster().createManager(getName()); 142 } catch (Exception ex) { 143 log.error(sm.getString("standardContext.cluster.managerError"), ex); 144 ok = false; 145 } 146 } else { 147 contextManager = new StandardManager(); 148 } 149 } 150 151 // Configure default manager if none was specified 152 if (contextManager != null) { 153 if (log.isDebugEnabled()) { 154 log.debug(sm.getString("standardContext.manager", 155 contextManager.getClass().getName())); 156 } 157 setManager(contextManager); 158 } 159 160 if (manager!=null && (getCluster() != null) && distributable) { 161 //let the cluster know that there is a context that is distributable 162 //and that it has its own manager 163 getCluster().registerManager(manager); 164 } 165 } 166 167 if (!getConfigured()) { 168 log.error(sm.getString("standardContext.configurationFail")); 169 ok = false; 170 } 171 172 /** 15.將Context的web資源添加到ServletContext中 */ 173 if (ok) 174 getServletContext().setAttribute 175 (Globals.RESOURCES_ATTR, getResources()); 176 177 /** 16.創建實例管理器,負責創建Servlet、Filter等實例*/ 178 if (ok ) { 179 if (getInstanceManager() == null) { 180 setInstanceManager(createInstanceManager()); 181 } 182 getServletContext().setAttribute( 183 InstanceManager.class.getName(), getInstanceManager()); 184 InstanceManagerBindings.bind(getLoader().getClassLoader(), getInstanceManager()); 185 } 186 187 /** 17.將jar包掃描器添加到ServletContext中 */ 188 if (ok) { 189 getServletContext().setAttribute( 190 JarScanner.class.getName(), getJarScanner()); 191 } 192 193 // Set up the context init params 194 mergeParameters(); 195 196 // Call ServletContainerInitializers 197 for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : 198 initializers.entrySet()) { 199 try { 200 entry.getKey().onStartup(entry.getValue(), 201 getServletContext()); 202 } catch (ServletException e) { 203 log.error(sm.getString("standardContext.sciFail"), e); 204 ok = false; 205 break; 206 } 207 } 208 209 /** 18.創建應用事件監聽器ApplicationListener */ 210 if (ok) { 211 if (!listenerStart()) { 212 log.error(sm.getString("standardContext.listenerFail")); 213 ok = false; 214 } 215 } 216 217 // Check constraints for uncovered HTTP methods 218 // Needs to be after SCIs and listeners as they may programmatically 219 // change constraints 220 if (ok) { 221 checkConstraintsForUncoveredMethods(findConstraints()); 222 } 223 224 try { 225 /** 19.啓動會話管理器 */ 226 Manager manager = getManager(); 227 if (manager instanceof Lifecycle) { 228 ((Lifecycle) manager).start(); 229 } 230 } catch(Exception e) { 231 log.error(sm.getString("standardContext.managerFail"), e); 232 ok = false; 233 } 234 235 /** 20.初始化Filter,並執行Filter的init方法 */ 236 if (ok) { 237 if (!filterStart()) { 238 log.error(sm.getString("standardContext.filterFail")); 239 ok = false; 240 } 241 } 242 243 /** 21. 加載並啓動Servlet,並執行Servlet的init方法 */ 244 if (ok) { 245 if (!loadOnStartup(findChildren())){ 246 log.error(sm.getString("standardContext.servletFail")); 247 ok = false; 248 } 249 } 250 251 /** 22.啓動後臺定時線程 */ 252 super.threadStart(); 253 } finally { 254 // Unbinding thread 255 unbindThread(oldCCL); 256 } 257 258 // Set available status depending upon startup success 259 if (ok) { 260 if (log.isDebugEnabled()) 261 log.debug("Starting completed"); 262 } else { 263 log.error(sm.getString("standardContext.startFailed", getName())); 264 } 265 266 startTime=System.currentTimeMillis(); 267 268 // Send j2ee.state.running notification 269 if (ok && (this.getObjectName() != null)) { 270 Notification notification = 271 new Notification("j2ee.state.running", this.getObjectName(), 272 sequenceNumber.getAndIncrement()); 273 broadcaster.sendNotification(notification); 274 } 275 276 /** 23.釋放資源*/ 277 getResources().gc(); 278 279 // Reinitializing if something went wrong 280 if (!ok) { 281 setState(LifecycleState.FAILED); 282 } else { 283 setState(LifecycleState.STARTING); 284 } 285 }
3.1.4、Connector啓動
Connector的startInternal方法實際就是調用了ProcotolHandler的start方法,而ProcotolHandler的start方法實際是調用了具體的Endpoint的start方法,以NioEndpoint爲例,實際最終執行的是NioEndpoint的startInternal方法,源碼如下:
1 /** NioEndpoint startInternal方法*/ 2 public void startInternal() throws Exception { 3 if (!running) { 4 running = true; 5 paused = false; 6 7 if (socketProperties.getProcessorCache() != 0) { 8 processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE, 9 socketProperties.getProcessorCache()); 10 } 11 if (socketProperties.getEventCache() != 0) { 12 eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE, 13 socketProperties.getEventCache()); 14 } 15 if (socketProperties.getBufferPool() != 0) { 16 nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE, 17 socketProperties.getBufferPool()); 18 } 19 20 /** 1.創建work線程 */ 21 if (getExecutor() == null) { 22 createExecutor(); 23 } 24 25 /** 2.初始化連接限制,值爲Connector的maxConnections配置 */ 26 initializeConnectionLatch(); 27 28 /** 3.創建並啓動Poller線程,作用是傳教啓動Selector監聽本地端口 */ 29 poller = new Poller(); 30 Thread pollerThread = new Thread(poller, getName() + "-ClientPoller"); 31 pollerThread.setPriority(threadPriority); 32 pollerThread.setDaemon(true); 33 pollerThread.start(); 34 35 /** 4.創建並啓動Acceptor線程,作用是接收客戶端連接請求 */ 36 startAcceptorThread(); 37 } 38 }
NioEndpoint中涉及到幾個對象,LimitLatch表示連接限制器,用於控制連接數不會超過最大連接數,默認最大爲10000,超過閾值會拒絕新的連接;Poller負責啓動Selector輪訓處理就緒的事件;Acceptor負責接收並處理客戶端連接請求。
LimitLatch是通過AQS的共享資源方法來控制併發,而重點是Poller線程和Acceptor線程
Poller線程處理邏輯如下:
1 public void run() { 2 /** 死循環處理直到Tomcat關閉*/ 3 while (true) { 4 5 boolean hasEvents = false; 6 try { 7 /** 如果沒有關閉 */ 8 if (!close) { 9 hasEvents = events(); 10 /** 1.1.如果被喚醒則立即執行select,不會阻塞線程 */ 11 if (wakeupCounter.getAndSet(-1) > 0) { 12 keyCount = selector.selectNow(); 13 } else { 14 /** 1.2.如果沒有喚醒則超時執行select */ 15 keyCount = selector.select(selectorTimeout); 16 } 17 //重置wakeupCount 18 wakeupCounter.set(0); 19 } 20 /** 如果已經關閉, 關閉Selector*/ 21 if (close) { 22 events(); 23 timeout(0, false); 24 try { 25 selector.close(); 26 } catch (IOException ioe) { 27 log.error(sm.getString("endpoint.nio.selectorCloseFail"), ioe); 28 } 29 break; 30 } 31 } catch (Throwable x) { 32 ExceptionUtils.handleThrowable(x); 33 log.error(sm.getString("endpoint.nio.selectorLoopError"), x); 34 continue; 35 } 36 // Either we timed out or we woke up, process events first 37 if (keyCount == 0) { 38 hasEvents = (hasEvents | events()); 39 } 40 41 Iterator<SelectionKey> iterator = 42 keyCount > 0 ? selector.selectedKeys().iterator() : null; 43 /** 遍歷處理器Selector監聽到的事件 */ 44 while (iterator != null && iterator.hasNext()) { 45 SelectionKey sk = iterator.next(); 46 NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment(); 47 if (socketWrapper == null) { 48 iterator.remove(); 49 } else { 50 iterator.remove(); 51 /** 處理IO讀寫事件 */ 52 processKey(sk, socketWrapper); 53 } 54 } 55 56 /** 處理超時事件 */ 57 timeout(keyCount,hasEvents); 58 } 59 60 getStopLatch().countDown(); 61 } 62 63 /** 是否有事件*/ 64 public boolean events() { 65 boolean result = false; 66 PollerEvent pe = null; 67 for (int i = 0, size = events.size(); i < size && (pe = events.poll()) != null; i++ ) { 68 result = true; 69 try { 70 /** 如果有PollerEvent事件,遍歷執行事件線程 */ 71 pe.run(); 72 pe.reset(); 73 if (running && !paused && eventCache != null) { 74 eventCache.push(pe); 75 } 76 } catch ( Throwable x ) { 77 log.error(sm.getString("endpoint.nio.pollerEventError"), x); 78 } 79 } 80 return result; 81 }
Poller線程就是通過Selector監聽IO事件,如果有IO事件就直接處理IO事件,並且還會處理PollerEvent事件
Acceptor線程處理邏輯如下:
1 public void run() { 2 3 int errorDelay = 0; 4 /** 死循環處理直到Tomcat關閉 */ 5 while (endpoint.isRunning()) { 6 7 /** 暫停Endpoint*/ 8 while (endpoint.isPaused() && endpoint.isRunning()) { 9 state = AcceptorState.PAUSED; 10 try { 11 Thread.sleep(50); 12 } catch (InterruptedException e) { 13 // Ignore 14 } 15 } 16 17 if (!endpoint.isRunning()) { 18 break; 19 } 20 state = AcceptorState.RUNNING; 21 22 try { 23 /** 1.檢驗連接數是否達到最大連接數,相當於提前獲取一個連接數的票據 */ 24 endpoint.countUpOrAwaitConnection(); 25 if (endpoint.isPaused()) { 26 continue; 27 } 28 29 U socket = null; 30 try { 31 /** 2.調用ServerSocket的accept方法接收客戶端連接請求並創建客戶端Socket */ 32 socket = endpoint.serverSocketAccept(); 33 } catch (Exception ioe) { 34 endpoint.countDownConnection(); 35 if (endpoint.isRunning()) { 36 // Introduce delay if necessary 37 errorDelay = handleExceptionWithDelay(errorDelay); 38 // re-throw 39 throw ioe; 40 } else { 41 break; 42 } 43 } 44 errorDelay = 0; 45 if (endpoint.isRunning() && !endpoint.isPaused()) { 46 /** 3.處理成功連接的socket*/ 47 if (!endpoint.setSocketOptions(socket)) { 48 endpoint.closeSocket(socket); 49 } 50 } else { 51 endpoint.destroySocket(socket); 52 } 53 } catch (Throwable t) { 54 ExceptionUtils.handleThrowable(t); 55 String msg = sm.getString("endpoint.accept.fail"); 56 if (t instanceof Error) { 57 Error e = (Error) t; 58 if (e.getError() == 233) { 59 log.warn(msg, t); 60 } else { 61 log.error(msg, t); 62 } 63 } else { 64 log.error(msg, t); 65 } 66 } 67 } 68 state = AcceptorState.ENDED; 69 }
主要邏輯就是先校驗最大連接數,然後通過ServerSocket的accept接收客戶端連接請求並初始化客戶端Socket,然後調用Endpoint的setSocketOptions方法設置客戶端socket,熟悉NIO的童鞋應該可以猜到該方法的邏輯就是要註冊socket的事件了。
NioEndpoint的setSocketOptions方法源碼如下:
/** 配置客戶端Socket */ protected boolean setSocketOptions(SocketChannel socket) { NioChannel channel = null; boolean success = false; try { /** 1.設置非阻塞 */ socket.configureBlocking(false); Socket sock = socket.socket(); socketProperties.setProperties(sock); /** 2.獲取或創建客戶端Channel*/ if (nioChannels != null) { channel = nioChannels.pop(); } if (channel == null) { SocketBufferHandler bufhandler = new SocketBufferHandler( socketProperties.getAppReadBufSize(), socketProperties.getAppWriteBufSize(), socketProperties.getDirectBuffer()); if (isSSLEnabled()) { channel = new SecureNioChannel(bufhandler, selectorPool, this); } else { channel = new NioChannel(bufhandler); } } /** 3.包裝客戶端Socket */ NioSocketWrapper socketWrapper = new NioSocketWrapper(channel, this); /** 4.存儲當前channel到Map中*/ connections.put(channel, socketWrapper); /** 5.重置channel信息 */ channel.reset(socket, socketWrapper); socketWrapper.setReadTimeout(getConnectionTimeout()); socketWrapper.setWriteTimeout(getConnectionTimeout()); socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests()); socketWrapper.setSecure(isSSLEnabled()); /** 6.將客戶端channel註冊到Selector中*/ poller.register(channel, socketWrapper); success = true; } catch (Throwable t) { ExceptionUtils.handleThrowable(t); try { log.error(sm.getString("endpoint.socketOptionsError"), t); } catch (Throwable tt) { ExceptionUtils.handleThrowable(tt); } } finally { if (!success && channel != null) { connections.remove(channel); channel.free(); } } return success; }
主要邏輯就是配置客戶端的Socket並和channel綁定,然後將channel註冊到選擇器Selector上,Poller類的註冊邏輯如下:
1 /** 註冊客戶端Channel到Selector進行監聽 */ 2 public void register(final NioChannel socket, final NioSocketWrapper socketWrapper) { 3 /** 1.註冊監聽OP_READ讀事件*/ 4 socketWrapper.interestOps(SelectionKey.OP_READ); 5 PollerEvent r = null; 6 /** 2.從緩存中獲取PollerEvent事件*/ 7 if (eventCache != null) { 8 r = eventCache.pop(); 9 } 10 /** 3.如果緩存中沒有就創建PollerEvent事件*/ 11 if (r == null) { 12 r = new PollerEvent(socket, OP_REGISTER); 13 } else { 14 r.reset(socket, OP_REGISTER); 15 } 16 /** 4.添加事件*/ 17 addEvent(r); 18 }
這裏會將註冊行爲封裝成PollerEvent事件,該事件是可執行任務實現了Runnable接口,然後將事件添加到Poller的事件隊列中交給Poller的線程執行。而PollerEvent的run方法執行邏輯如下:
/** PollerEvent 執行邏輯 */ public void run() { /** 1.如果感興趣事件爲註冊事件*/ if (interestOps == OP_REGISTER) { try { /** 2.註冊讀事件到Selector中*/ socket.getIOChannel().register(socket.getSocketWrapper().getPoller().getSelector(), SelectionKey.OP_READ, socket.getSocketWrapper()); } catch (Exception x) { log.error(sm.getString("endpoint.nio.registerFail"), x); } } else { /** 2.如果不是註冊事件,則取消事件監聽*/ final SelectionKey key = socket.getIOChannel().keyFor(socket.getSocketWrapper().getPoller().getSelector()); try { if (key == null) { try { socket.socketWrapper.close(); } catch (Exception ignore) { } } else { final NioSocketWrapper socketWrapper = (NioSocketWrapper) key.attachment(); if (socketWrapper != null) { // We are registering the key to start with, reset the fairness counter. int ops = key.interestOps() | interestOps; socketWrapper.interestOps(ops); key.interestOps(ops); } else { socket.getSocketWrapper().getPoller().cancelledKey(key, socket.getSocketWrapper()); } } } catch (CancelledKeyException ckx) { try { socket.getSocketWrapper().getPoller().cancelledKey(key, socket.getSocketWrapper()); } catch (Exception ignore) {} } } }
真正的執行了channel註冊到Selector的邏輯,監聽事件爲OR_READ讀事件
總結:
1、Connector啓動執行了startInternal方法,調用了ProcotolHandler實例的start方法;
2、ProtocolHandler的start方法,調用了AbstractEndpoint的start方法,AbstractEndpoint執行bindWithCleanup方法,實際調用實現類的bind方法;
3、NioEndpoint的bind方法初始化服務器ServerSocket和Selector;然後執行NioEndpoint的startInternal方法
4、NioEndpoint的startInternal初始化最大連接數,初始化Poller線程和Acceptor線程
5、Poller線程負責死循環處理Selector監聽到的Channel中的事件並處理,同時還需要處理客戶端Channel的註冊到Selector的邏輯
6、Acceptor線程負責接收客戶端連接請求,初始化客戶端Socket和Channel,並封裝成註冊事件PollerEvent交給Poller線程處理
7、PollerEvent運行邏輯是將客戶端channel和ON_READ讀事件註冊到Poller監聽的Selector上
所以Connector啓動相當於服務器綁定指定端口監聽客戶端連接事件並初始化Selector,然後將客戶端連接和讀事件註冊到Selector上監聽,然後就一直通過Selector監聽客戶端Channel的可讀事件即可處理客戶端的請求了。
3.2、Tomcat處理請求原理
客戶端連接服務器之後,服務器Selector會一直監聽客戶端Channel的ON_READ事件,讀取客戶端請求。讀取到請求之後執行processKey方法處理,如果OperationState存在就交給OperationState的process方法來處理,實際是由子類處理,如NioOperationState。
run方法執行邏輯如下:
1 public void run() { 2 long nBytes = 0; 3 if (getError() == null) { 4 try { 5 synchronized (this) { 6 if (read) { 7 /** 1.處理讀事件 */ 8 if (!socketBufferHandler.isReadBufferEmpty()) { 9 socketBufferHandler.configureReadBufferForRead(); 10 /** 1.1.從緩衝區讀取數據*/ 11 for (int i = 0; i < length && !socketBufferHandler.isReadBufferEmpty(); i++) { 12 nBytes += transfer(socketBufferHandler.getReadBuffer(), buffers[offset + i]); 13 } 14 } 15 if (nBytes == 0) { 16 /** 1.2.執行read方法處理數據 */ 17 nBytes = getSocket().read(buffers, offset, length); 18 updateLastRead(); 19 } 20 } else { 21 /** 2.處理寫事件 */ 22 boolean doWrite = true; 23 if (!socketBufferHandler.isWriteBufferEmpty()) { 24 socketBufferHandler.configureWriteBufferForRead(); 25 do { 26 /** 2.2.執行write方法寫數據*/ 27 nBytes = getSocket().write(socketBufferHandler.getWriteBuffer()); 28 } while (!socketBufferHandler.isWriteBufferEmpty() && nBytes > 0); 29 if (!socketBufferHandler.isWriteBufferEmpty()) { 30 doWrite = false; 31 } 32 // Preserve a negative value since it is an error 33 if (nBytes > 0) { 34 nBytes = 0; 35 } 36 } 37 if (doWrite) { 38 nBytes = getSocket().write(buffers, offset, length); 39 updateLastWrite(); 40 } 41 } 42 if (nBytes != 0) { 43 completionDone = false; 44 } 45 } 46 } catch (IOException e) { 47 setError(e); 48 } 49 } 50 if (nBytes > 0) { 51 // The bytes processed are only updated in the completion handler 52 completion.completed(Long.valueOf(nBytes), this); 53 } else if (nBytes < 0 || getError() != null) { 54 IOException error = getError(); 55 if (error == null) { 56 error = new EOFException(); 57 } 58 completion.failed(error, this); 59 } else { 60 // As soon as the operation uses the poller, it is no longer inline 61 inline = false; 62 if (read) { 63 registerReadInterest(); 64 } else { 65 registerWriteInterest(); 66 } 67 } 68 }
如果OperationState不存在,就執行processSocket方法處理,源碼如下:
1 public boolean processSocket(SocketWrapperBase<S> socketWrapper, 2 SocketEvent event, boolean dispatch) { 3 try { 4 if (socketWrapper == null) { 5 return false; 6 } 7 SocketProcessorBase<S> sc = null; 8 /** 1.從緩存中獲取SocketProcessorBase對象*/ 9 if (processorCache != null) { 10 sc = processorCache.pop(); 11 } 12 /** 13 * 2.緩存不存在就新建;緩存中存在就重置 14 * */ 15 if (sc == null) { 16 sc = createSocketProcessor(socketWrapper, event); 17 } else { 18 sc.reset(socketWrapper, event); 19 } 20 /** 3.如果有線程池就將SocketProcessorBase任務交給線程池; 21 * 如果沒有線程池就執行運行SocketProcessorBase*/ 22 Executor executor = getExecutor(); 23 if (dispatch && executor != null) { 24 executor.execute(sc); 25 } else { 26 sc.run(); 27 } 28 } catch (Throwable t) { 29 } 30 return true; 31 }
這裏會將請求交給SocketProcessorBase來處理,而SocketProcessorBase最終會執行子類的doRun方法處理,如NioEndPoint的內部類SocketProcessor,處理邏輯如下:
1 protected void doRun() { 2 /** 1.客戶端Socket Channel*/ 3 NioChannel socket = socketWrapper.getSocket(); 4 /** 2.客戶端的事件SelectKey*/ 5 SelectionKey key = socket.getIOChannel().keyFor(socket.getSocketWrapper().getPoller().getSelector()); 6 Poller poller = NioEndpoint.this.poller; 7 if (poller == null) { 8 socketWrapper.close(); 9 return; 10 } 11 12 try { 13 /** 3.客戶端握手狀態 */ 14 int handshake = -1; 15 try { 16 if (key != null) { 17 if (socket.isHandshakeComplete()) { 18 handshake = 0; 19 } else if (event == SocketEvent.STOP || event == SocketEvent.DISCONNECT || 20 event == SocketEvent.ERROR) { 21 handshake = -1; 22 } else { 23 /** 4.如果當前沒有握手,則先處理客戶端握手操作*/ 24 handshake = socket.handshake(key.isReadable(), key.isWritable()); 25 event = SocketEvent.OPEN_READ; 26 } 27 } 28 } catch (IOException x) { 29 handshake = -1; 30 if (log.isDebugEnabled()) log.debug("Error during SSL handshake",x); 31 } catch (CancelledKeyException ckx) { 32 handshake = -1; 33 } 34 /** 5.根據握手狀態處理請求 */ 35 if (handshake == 0) { 36 //5.1.握手成功處理請求 37 SocketState state = SocketState.OPEN; 38 //處理事件 39 if (event == null) { 40 state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ); 41 } else { 42 state = getHandler().process(socketWrapper, event); 43 } 44 if (state == SocketState.CLOSED) { 45 poller.cancelledKey(key, socketWrapper); 46 } 47 } else if (handshake == -1 ) { 48 //5.2.握手失敗取消請求 49 getHandler().process(socketWrapper, SocketEvent.CONNECT_FAIL); 50 poller.cancelledKey(key, socketWrapper); 51 } else if (handshake == SelectionKey.OP_READ){ 52 //5.3.讀就緒註冊讀事件 53 socketWrapper.registerReadInterest(); 54 } else if (handshake == SelectionKey.OP_WRITE){ 55 //5.4.寫就緒註冊寫事件 56 socketWrapper.registerWriteInterest(); 57 } 58 } catch (CancelledKeyException cx) { 59 poller.cancelledKey(key, socketWrapper); 60 } catch (VirtualMachineError vme) { 61 ExceptionUtils.handleThrowable(vme); 62 } catch (Throwable t) { 63 log.error(sm.getString("endpoint.processing.fail"), t); 64 poller.cancelledKey(key, socketWrapper); 65 } finally { 66 socketWrapper = null; 67 event = null; 68 //return to cache 69 if (running && !paused && processorCache != null) { 70 processorCache.push(this); 71 } 72 } 73 }
這裏主要是根據客戶端的握手狀態判斷是否要處理請求,如果沒有握手就先握手,如果握手失敗就取消請求,如果握手成功就處理請求。處理請求的邏輯交給了Handler來處理,實現是ConnectionHandler,處理邏輯是process方法,方法邏輯比較長,核心邏輯如下:
/** ConnectionHandler處理客戶端請求 */ public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) { S socket = wrapper.getSocket(); /** 1.獲取封裝socket的處理器,如HTTP/1.1對應的就是Http11Processor */ Processor processor = (Processor) wrapper.getCurrentProcessor(); /** 2.標記主線程*/ ContainerThreadMarker.set(); try { /** 3.如果處理器不存在,那麼就根據當前請求的協議來初始化處理器*/ if (processor == null) { String negotiatedProtocol = wrapper.getNegotiatedProtocol(); if (negotiatedProtocol != null && negotiatedProtocol.length() > 0) { UpgradeProtocol upgradeProtocol = getProtocol().getNegotiatedProtocol(negotiatedProtocol); if (upgradeProtocol != null) { processor = upgradeProtocol.getProcessor( wrapper, getProtocol().getAdapter()); } else if (negotiatedProtocol.equals("http/1.1")) { } else { return SocketState.CLOSED; } } } /** 4.從緩存隊列中獲取Processor,如果獲取不到就通過createProcessor創建*/ if (processor == null) { processor = recycledProcessors.pop(); } if (processor == null) { processor = getProtocol().createProcessor(); register(processor); } processor.setSslSupport(wrapper.getSslSupport(getProtocol().getClientCertProvider())); /** 5.將socket和處理器關聯*/ wrapper.setCurrentProcessor(processor); SocketState state = SocketState.CLOSED; do { /** 6.執行處理器的process方法處理請求 */ state = processor.process(wrapper, status); if (state == SocketState.UPGRADING) { // HTTP協議升級處理 } } while ( state == SocketState.UPGRADING); /** 7.根據執行結果後置處理*/ if (state == SocketState.LONG) { longPoll(wrapper, processor); if (processor.isAsync()) { getProtocol().addWaitingProcessor(processor); } } else if (state == SocketState.OPEN) { wrapper.setCurrentProcessor(null); release(processor); wrapper.registerReadInterest(); } else if (state == SocketState.SENDFILE) { } else if (state == SocketState.UPGRADED) { if (status != SocketEvent.OPEN_WRITE) { longPoll(wrapper, processor); getProtocol().addWaitingProcessor(processor); } } else if (state == SocketState.SUSPENDED) { //...... } else { //連接關閉,釋放processor wrapper.setCurrentProcessor(null); //...... release(processor); } return state; } catch (ProtocolException e) { } /** 釋放processor */ wrapper.setCurrentProcessor(null); release(processor); return SocketState.CLOSED; }
核心邏輯是獲取請求對應協議的處理器,如果HTTP1.1協議處理器是Http11Processor,然後調用處理器的process方法真正的處理請求,以Http11Processor爲例,首先先執行父類AbstractProcessor的process方法,然後調用了Adapter的asyncDispatch方法。
最終執行了connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)方法。這裏涉及到Tomcat的責任鏈模式,每一層容器都有一個pipeline,每一個pipeline都包含了Valve,具體執行交給具體的Valve執行。
首先是connector.getService().getContainer()方法返回Engine對象,執行invoke方法實際就是執行了StandardEngineValve的invoke方法,該方法繼續調用StandardHostValve的invoke方法,然後執行StandardContextValve的invoke方法,最後執行了
StandardWrapperValve的invoke方法,wrapper是Tomcat容器的最小容器,所以這裏就需要處理具體的邏輯了, 核心處理邏輯如下:
1 public final void invoke(Request request, Response response) 2 throws IOException, ServletException { 3 4 boolean unavailable = false; 5 StandardWrapper wrapper = (StandardWrapper) getContainer(); 6 Servlet servlet = null; 7 Context context = (Context) wrapper.getParent(); 8 9 try { 10 /** 1.初始化Servlet */ 11 if (!unavailable) { 12 servlet = wrapper.allocate(); 13 } 14 } catch (UnavailableException e) { 15 //異常處理 16 } catch (ServletException e) { 17 } catch (Throwable e) { 18 } 19 20 MessageBytes requestPathMB = request.getRequestPathMB(); 21 DispatcherType dispatcherType = DispatcherType.REQUEST; 22 if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC; 23 request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,dispatcherType); 24 request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, 25 requestPathMB); 26 /** 2.創建過濾器鏈路 */ 27 ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet); 28 Container container = this.container; 29 try { 30 if ((servlet != null) && (filterChain != null)) { 31 if (context.getSwallowOutput()) { 32 try { 33 if (request.isAsyncDispatching()) { 34 request.getAsyncContextInternal().doInternalDispatch(); 35 } else { 36 /** 3.執行過濾器鏈 */ 37 filterChain.doFilter(request.getRequest(), response.getResponse()); 38 } 39 } finally { 40 } 41 } else { 42 if (request.isAsyncDispatching()) { 43 request.getAsyncContextInternal().doInternalDispatch(); 44 } else { 45 filterChain.doFilter(request.getRequest(), response.getResponse()); 46 } 47 } 48 49 } 50 } catch (ClientAbortException | CloseNowException e) { 51 //異常處理 52 } catch (IOException e) { 53 } catch (UnavailableException e) { 54 } catch (ServletException e) { 55 } catch (Throwable e) { 56 } finally { 57 //釋放資源 58 } 59 }
核心邏輯分別是執行wrapper.allocation初始化Servlet,然後調用ApplicationFilterChain的doFilter方法執行過濾器鏈,初始化Servlet方法會創建Servlet對象並執行Servlet的init方法,而執行過濾器鏈方法如下:
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if( Globals.IS_SECURITY_ENABLED ) { final ServletRequest req = request; final ServletResponse res = response; try { java.security.AccessController.doPrivileged( new java.security.PrivilegedExceptionAction<Void>() { @Override public Void run() throws ServletException, IOException { internalDoFilter(req,res); return null; } } ); } catch( PrivilegedActionException pe) { } } else { internalDoFilter(request,response); } } private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if (pos < n) { ApplicationFilterConfig filterConfig = filters[pos++]; try { Filter filter = filterConfig.getFilter(); if (request.isAsyncSupported() && "false".equalsIgnoreCase( filterConfig.getFilterDef().getAsyncSupported())) { request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE); } if( Globals.IS_SECURITY_ENABLED ) { final ServletRequest req = request; final ServletResponse res = response; Principal principal = ((HttpServletRequest) req).getUserPrincipal(); Object[] args = new Object[]{req, res, this}; SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal); } else { /** 1.依次執行所有Filter的doFilter方法 */ filter.doFilter(request, response, this); } } catch (IOException | ServletException | RuntimeException e) { throw e; } return; } try { if (request.isAsyncSupported() && !servletSupportsAsync) { request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE); } if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse) && Globals.IS_SECURITY_ENABLED ) { final ServletRequest req = request; final ServletResponse res = response; Principal principal = ((HttpServletRequest) req).getUserPrincipal(); Object[] args = new Object[]{req, res}; /** 2.執行servlet的service方法*/ SecurityUtil.doAsPrivilege("service",servlet, classTypeUsedInService, args,principal); } else { /** 2.執行servlet的service方法*/ servlet.service(request, response); } } catch (IOException | ServletException | RuntimeException e) { throw e; } catch (Throwable e) { } finally { } }
ApplicationFilterChain的doFilter實際就依次執行了所有的Filter的doFilter方法,然後執行了Servlet的service方法,而Servlet的service實際就是用於處理具體的業務邏輯的。
四、Tomcat進階
4.1、Tomcat配置優化
1、JVM配置優化
Tomcat本質是一個JVM進程,所以可以通過優化JVM配置參數來對Tomcat進行性能優化,可以參考JVM配置調優的方式優化,本文不再贅述。
2、Connector配置優化
Connector配置比較多,可以從多個參數來進行優化
maxThreads:最大線程數
acceptCount:接收等待請求數
maxConnections:最大連接數
executor:線程池
3、IO配置優化
Tomcat提高了BIO、NIO和APR三種IO通信模式,Tomcat7默認是BIO,但是Tomcat8默認是NIO,NIO的通信效率要遠遠大於BIO的通信效率。APR默認需要系統中安裝了APR纔可以支持,但是效率最高。
4.2、Tomcat異步處理
Tomcat處理請求默認是同步處理,每個請求都需要一個Tomcat線程去處理,而Tomcat線程數是有限的,當大量併發請求過來時,線程數超過Tomcat最大線程數,那麼多餘的請求就會處於等待狀態。而異步Servlet的處理就是接收請求的是Tomcat線程,但是處理業務的多是應用線程,可以採用線程池。這樣Tomcat線程只需要接收請求即可,而不需要處理業務邏輯,這樣就大大提高了Tomcat的整體吞吐量。但是客戶端單次請求的耗時並不會減少,反而可能會增加,因爲多了線程上下文切換的消耗。
測試案例如下:
@WebServlet(name = "asyncServlet", urlPatterns = "/async", asyncSupported=true) public class AsyncServlet extends HttpServlet { ExecutorService executorService = Executors.newFixedThreadPool(20); @Override public void doGet(HttpServletRequest req, HttpServletResponse res){ try { final AsyncContext context = req.startAsync(); executorService.execute(new Runnable() { @Override public void run() { try { Thread.sleep(1000L); context.getResponse().getWriter().println("異步處理成功"); context.complete(); } catch (Exception e) { e.printStackTrace(); } } }); }catch (Exception e){ e.printStackTrace(); } } }
異步Servlet需要註解@WebServlet配置asyncSupported=true,處理業務時首先調用HttpServletRequest的startAsync()開啓異步處理,然後將業務邏輯交給線程池,在線程池的線程中處理業務返回結果,然後調用異步上下文AsyncContext的complete()完成異步處理。
所以異步Servlet的處理核心就在HttpServletRequest的startAsync方法和AsyncContext的complete方法。
startAsync方法是獲取一個異步上下文AsyncContext對象,代碼如下:
1 /** 開啓異步,獲取異步上下文*/ 2 public AsyncContext startAsync(ServletRequest request, ServletResponse response) { 3 /** 1.判斷是否支持異步*/ 4 if (!isAsyncSupported()) { 5 IllegalStateException ise = 6 new IllegalStateException(sm.getString("request.asyncNotSupported")); 7 log.warn(sm.getString("coyoteRequest.noAsync", 8 StringUtils.join(getNonAsyncClassNames())), ise); 9 throw ise; 10 } 11 /** 2.創建異步上下文對象 */ 12 if (asyncContext == null) { 13 asyncContext = new AsyncContextImpl(this); 14 } 15 /** 3.上下文屬性設置 */ 16 asyncContext.setStarted(getContext(), request, response, request==getRequest() && response==getResponse().getResponse()); 17 asyncContext.setTimeout(getConnector().getAsyncTimeout()); 18 return asyncContext; 19 }
當處理完業務邏輯後,執行complete方法完成操作,和同步請求的處理邏輯類似。
4.3、如何設計一個Tomcat?
設計一個Tomcat首先需要梳理完成一個Tomcat需要哪些組件,核心組件如下:
1、連接器:監聽端口,接收客戶端連接和請求;
2、編解碼器:將客戶端請求根據不同的協議進行編解碼;
3、業務處理器:用於處理客戶端請求,返回處理結果
4、路徑映射:將接口路徑和業務處理器進行綁定映射;
5、請求轉發:根據請求路徑將客戶端分配給指定業務處理方法;
整體設計圖如下:
連接器Connector負責監聽本地端口,接收客戶端請求,將客戶端發送來的請求通過編解碼器按指定通信協議解析,如將HTTP協議的請求解析成HTTP請求交給請求轉發器,請求轉發器根據路徑映射關係,將請求分配給指
定的業務處理器進行處理同時返回處理結果。
Netty作爲目前最熱門的NIO通信框架,就可以作爲Tomcat服務器的基本框架來實現Tomcat的功能。
首先Netty服務器開啓boss線程池監聽客戶端連接請求,然後將客戶端業務請求交給work線程池處理,同時Netty封裝了多種通信協議編解碼的封裝,而業務處理器可以用Servlet或SpringMVC,剩下只剩下請求轉發器和路徑
映射需要自行實現即可。
基於Netty實現簡易Tomcat源碼如下:
public class TomcatServer { /** 路徑映射關係 */ static Map<String, UrlMapping> controllerMap = new HashMap<>(); /** 業務處理線程池 */ static ExecutorService executorService = Executors.newFixedThreadPool(50); public static void main(String[] args) throws InterruptedException { /** 1.啓動Spring容器 */ ApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); /** 2.解析Controller路徑映射關係 */ parseMapping(context); /** 3.啓動Netty */ startServer(8000); } /** 解析Spring容器中Controller的接口方法*/ private static void parseMapping(ApplicationContext context){ /** 1.遍歷所有被@Controller註解修飾的bean*/ for(String beanName:context.getBeanNamesForAnnotation(Controller.class)){ Object bean = context.getBean(beanName); RestController controller = bean.getClass().getAnnotation(RestController.class); String basePath = controller.value(); Method[] methods = bean.getClass().getMethods(); for(Method method : methods){ /** 2.遍歷Controller中所有被@RequestMapping註解修飾的方法 */ RequestMapping requestMapping = null; if((requestMapping=method.getAnnotation(RequestMapping.class))!=null){ String methodPath = requestMapping.value()[0]; UrlMapping urlMapping = new UrlMapping(); urlMapping.setController(bean); urlMapping.setMethod(method); urlMapping.setUrl(basePath + "/" + methodPath); /** 3.緩存URL和方法的映射關係 */ controllerMap.put(urlMapping.getUrl(), urlMapping); } } } } private static void startServer(int port) throws InterruptedException { /** 初始化服務器引導對象*/ ServerBootstrap bootstrap = new ServerBootstrap(); int processorsCount = Runtime.getRuntime().availableProcessors(); /** 初始化boss線程池,線程數爲2*/ NioEventLoopGroup bossGroup = new NioEventLoopGroup(2); /** 初始化worker線程池,線程數爲CPU核數兩倍*/ NioEventLoopGroup workGroup = new NioEventLoopGroup(processorsCount * 2); /** 設置boss線程池和work線程池 */ bootstrap.group(bossGroup, workGroup); /** 設置boss線程池在的Channel實現類類型 */ bootstrap.channel(NioServerSocketChannel.class); /** 設置ServerChannel的配置*/ bootstrap.option(ChannelOption.SO_BACKLOG, 1024); /** 主線程ServerChannel添加ChannelHandler */ bootstrap.handler(new LoggingHandler()); /** 初始化客戶端Channel的ChannelPipeline和ChannelHandler */ bootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { /** 創建ChannelPipeline對象 */ ChannelPipeline pipeline = socketChannel.pipeline(); /** channelPipeline添加HTTP編解碼相關handler*/ pipeline.addLast(new HttpServerCodec()); pipeline.addLast(new ChunkedWriteHandler()); pipeline.addLast(new HttpObjectAggregator(64 * 1024)); /** 自定義業務請求處理器*/ pipeline.addLast(new TomcatHttpHandler()); } }); /** 綁定端口號 */ bootstrap.bind(port).sync(); } /** HTTP請求處理器 */ static class TomcatHttpHandler extends ChannelInboundHandlerAdapter{ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if(msg instanceof FullHttpRequest){ /** 根據請求路徑找到對應的映射關係 */ FullHttpRequest request = (FullHttpRequest)msg; QueryStringDecoder decoder = new QueryStringDecoder(request.uri()); Map<String, Object> params = new HashMap<>(); decoder.parameters().entrySet().forEach((v)->{ params.put(v.getKey(), v.getValue().get(0)); }); String url = request.uri().replaceFirst("/",""); String path = url.substring(0, url.indexOf("?")); UrlMapping mapping = controllerMap.get(path); if(mapping != null){ executorService.execute(new Runnable() { @Override public void run() { try { /** 反射調用映射的Controller的指定方法 */ Object result = mapping.getMethod().invoke(mapping.getController(), params.values().toArray()); FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND); response.headers().set("Content-Type", "application/json"); response.content().writeBytes(JSON.toJSONString(result).getBytes()); /** 返回處理結果*/ ctx.writeAndFlush(response); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }); } }else { FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND); response.headers().set("Content-Type", "text/html;"); ctx.writeAndFlush(response); } } } }
接口路徑映射關係實體類
public class UrlMapping { /** 路徑*/ private String url; /** controller*/ private Object controller; /** 方法*/ private Method method; public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public Object getController() { return controller; } public void setController(Object controller) { this.controller = controller; } public Method getMethod() { return method; } public void setMethod(Method method) { this.method = method; } }
業務層Controller
1 @RestController(value = "goods") 2 public class GoodsController { 3 4 @RequestMapping(value = "query") 5 public Goods queryGoods(String goodsCode){ 6 Goods goods = new Goods(); 7 goods.setGoodsCode(goodsCode); 8 goods.setGoodsName("測試商品"); 9 return goods; 10 } 11 12 13 static class Goods{ 14 private String goodsCode; 15 private String goodsName; 16 17 public String getGoodsCode() { 18 return goodsCode; 19 } 20 21 public void setGoodsCode(String goodsCode) { 22 this.goodsCode = goodsCode; 23 } 24 25 public String getGoodsName() { 26 return goodsName; 27 } 28 29 public void setGoodsName(String goodsName) { 30 this.goodsName = goodsName; 31 } 32 } 33 }
測試請求路徑 http://localhost:8000/goods/query?goodsCode=test 測試結果如下: