淺析Tomcat工作流程
必讀20遍好文章:
領悟:https://www.oschina.net/question/12_52027
Tomcat:https://www.ibm.com/developerworks/cn/java/j-lo-servlet/
Tomcat:https://www.ibm.com/developerworks/cn/java/j-lo-tomcat1/
Cookie:https://mp.weixin.qq.com/s/NXrH7R8y2Dqxs9Ekm0u33w?
按照大佬給的步驟一頓操作猛如虎,但是自己操作完之后就開始問了自己幾個問題,哪里不會問哪里。
<?xml version='1.0' encoding='utf-8'?> <Server port="8005" shutdown="SHUTDOWN"> <Service name="Catalina"> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8"/> <Engine name="Catalina" defaultHost="localhost"> <Host name="localhost"> <Context path="" docBase="C:\Users\admin\Desktop\ServletBase\ServletDemo\webapp" reloadable="true"/> </Host> </Engine> </Service> </Server>
Context代表了運行在Host上的單個Web應用,一個Host可以有多個Context元素,每個Web應用必須有唯一的URL路徑,這個URL路徑在Context中的path屬性中設定。
path:指定訪問該Web應用的URL入口,web項目的訪問路徑,不填默認以http://localhost:8080/開始。
docBase:指定Web應用的文件路徑,可以給定絕對路徑,也可以給定相對於<Host>的appBase屬性的相對路徑,如果Web應用采用開放目錄結構,則指定Web應用的根目錄,如果Web應用是個war文件,則指定war文件的路徑,你要讓tomcat幫你管理Servlet你總要告訴它Servlet在哪的。
reloadable:如果這個屬性設為true,tomcat服務器在運行狀態下會監視在WEB-INF/classes和WEB-INF/lib目錄下class文件的改動,如果監測到有class文件被更新的,服務器會自動重新加載Web應用。在開發階段將reloadable屬性設為true,有助於調試servlet和其它的class文件,但這樣用加重服務器運行負荷,建議在Web應用的發存階段將reloadable設為false。
圖1讓我理解了Tomcat整體的結構,在整個Tomcat中,被容器的概念貫穿,四層結構Engine,Host,Context,Weapper,從頂層Engine開始,到Host,再到Context,最后是Wrapper,每一層都是一個容器。每一個Context都代表着一個單獨的web項目。在Context中為了解耦,將Servlet封裝在Tomcat自身的Wrapper容器里,使得Tomcat與Servlet的關聯不在緊密。
圖2是讓我印象最深的一張圖,它幾乎描述了整個Tomcat的大致工作流程。在理解這張圖之前,記得要Tomcat的源碼下載下來,我下載的是Tomcat7版本的源碼,然后將源碼中java文件夾下的javax和org分別導入Eclipse中,這樣很方便查看。從最開始我們點擊startup.bat,喚起Tomcat,然后Tomcat在啟動(org.apache.catalina.startup.Tomcat)過程中會讀取server.xml,然后初始化Server,Service,引擎,還有Connector,Server和Service不必講也都明白,Engine在這里有必要介紹一下,它的責任就是將用戶請求分配給一個虛擬機處理,而Connector的作用就是建立一個連接,一個WEB服務器到Tomcat的連接器,在圖的最下面可以看到在這個Connector容器里初始化Http服務。
當Tomcat自身基礎搭建好之后,開始針對web應用做文章了。做文章之前先啟動了自身的服務然后初始化Host容器,啟動Host,在Host啟動過程中初始化了一個web應用上下文環境(回看上一章節)即StandardContext,到這你要注意了,當StandardContext的狀態變為init時(也就是server.xml中的Context節點被讀取並初始化時),ContextConfig作為觀察者將被通知,你該問?是啥時候給StandardContext添加了這么一個觀察者啊?這段代碼在Tomcat啟動時就已經添加了,我們來看一下這段代碼:
// 這是手動啟動Tomcat下面examples項目的例子,真實的啟動Tomcat也大致類似 Tomcat tomcat = getTomcatInstance(); File appDir = new File(getBuildDirectory(), "webapps/examples"); tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath()); tomcat.start();
上面只是一段從創建Tomcat實例,到調用addWebapp()的簡短過程,我們的主要關注點在addWebapp里:
public Context addWebapp(Host host, String contextPath, String docBase) { LifecycleListener listener = null; try { // 使用獲取一個上下文配置即ContextConfig【繼承了LifecycleListener接口】 Class<?> clazz = Class.forName(getHost().getConfigClass()); listener = (LifecycleListener) clazz.newInstance(); } catch (Exception e) { // Wrap in IAE since we can't easily change the method signature to // to throw the specific checked exceptions throw new IllegalArgumentException(e); } return addWebapp(host, contextPath, docBase, listener); } public Context addWebapp(Host host, String contextPath, String docBase, LifecycleListener config) { silence(host, contextPath); Context ctx = createContext(host, contextPath); ctx.setPath(contextPath); ctx.setDocBase(docBase); // 返回配置默認JSP處理的偵聽器對象。背后是讀取全局web.xml,啟動JspServlet和DefaultServlet ctx.addLifecycleListener(getDefaultWebXmlListener()); ctx.setConfigFile(getWebappConfigFile(docBase, contextPath)); // 添加一個上下文配置的監聽器,當Context變為init時被觸發 ctx.addLifecycleListener(config); if (config instanceof ContextConfig) { // prevent it from looking ( if it finds one - it'll have dup error ) ((ContextConfig) config).setDefaultWebXml(noDefaultWebXmlPath()); } if (host == null) { getHost().addChild(ctx); } else { host.addChild(ctx); } return ctx; }
在上面這段代碼的第22行,在Tomcat初始化的時候創建了一個StandardContext,並給StandardCOntext添加了ContextConfig觀察者(5,6,29)。
// org.apache.catalina.startup.Tomcat.java // 在Tomcat啟動時啟動JspServlet和DefaultServlet(只有當load-on-startup的value值>0時才會被啟動) public LifecycleListener getDefaultWebXmlListener() { return new DefaultWebXmlListener(); } public static class DefaultWebXmlListener implements LifecycleListener { @Override public void lifecycleEvent(LifecycleEvent event) { if (Lifecycle.BEFORE_START_EVENT.equals(event.getType())) { initWebappDefaults((Context) event.getLifecycle()); } } } public static void initWebappDefaults(Context ctx) { // Default servlet,它的load-on-startup值為1 Wrapper servlet = addServlet( ctx, "default", "org.apache.catalina.servlets.DefaultServlet"); servlet.setLoadOnStartup(1); servlet.setOverridable(true); // JSP servlet (by class name - to avoid loading all deps),它的load-on-startup值為3 servlet = addServlet( ctx, "jsp", "org.apache.jasper.servlet.JspServlet"); servlet.addInitParameter("fork", "false"); servlet.setLoadOnStartup(3); servlet.setOverridable(true); // Servlet mappings ctx.addServletMapping("/", "default"); ctx.addServletMapping("*.jsp", "jsp"); ctx.addServletMapping("*.jspx", "jsp"); // Sessions ctx.setSessionTimeout(30); // MIME mappings for (int i = 0; i < DEFAULT_MIME_MAPPINGS.length;) { ctx.addMimeMapping(DEFAULT_MIME_MAPPINGS[i++], DEFAULT_MIME_MAPPINGS[i++]); } // Welcome files ctx.addWelcomeFile("index.html"); ctx.addWelcomeFile("index.htm"); ctx.addWelcomeFile("index.jsp"); }
當StandardContext變為init時,ContextConfig這個觀察者被通知,然后這個觀察者的lifecycleEvent方法( package org.apache.catalina.startup.ContextConfig.lifecycleEvent() )被觸發,lifecycleEvent()->configureStart()->webConfig():
protected void webConfig() { Set<WebXml> defaults = new HashSet<WebXml>(); defaults.add(getDefaultWebXmlFragment()); WebXml webXml = createWebXml(); // Parse context level web.xml InputSource contextWebXml = getContextWebXmlSource(); parseWebXml(contextWebXml, webXml, false); ServletContext sContext = context.getServletContext(); // Ordering is important here // Step 1. Identify all the JARs packaged with the application // If the JARs have a web-fragment.xml it will be parsed at this // point. Map<String,WebXml> fragments = processJarsForWebFragments(webXml); // Step 2. Order the fragments. Set<WebXml> orderedFragments = null; orderedFragments = WebXml.orderWebFragments(webXml, fragments, sContext); // Step 3. Look for ServletContainerInitializer implementations if (ok) { processServletContainerInitializers(); } if (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) { // Steps 4 & 5. processClasses(webXml, orderedFragments); } if (!webXml.isMetadataComplete()) { // Step 6. Merge web-fragment.xml files into the main web.xml // file. if (ok) { ok = webXml.merge(orderedFragments); } // Step 7. Apply global defaults // Have to merge defaults before JSP conversion since defaults // provide JSP servlet definition. webXml.merge(defaults); // Step 8. Convert explicitly mentioned jsps to servlets if (ok) { convertJsps(webXml); } // Step 9. Apply merged web.xml to Context if (ok) { webXml.configureContext(context); } } else { webXml.merge(defaults); convertJsps(webXml); webXml.configureContext(context); } // Step 9a. Make the merged web.xml available to other // components, specifically Jasper, to save those components // from having to re-generate it. // TODO Use a ServletContainerInitializer for Jasper String mergedWebXml = webXml.toXml(); sContext.setAttribute( org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML, mergedWebXml); if (context.getLogEffectiveWebXml()) { log.info("web.xml:\n" + mergedWebXml); } // Always need to look for static resources // Step 10. Look for static resources packaged in JARs if (ok) { // Spec does not define an order. // Use ordered JARs followed by remaining JARs Set<WebXml> resourceJars = new LinkedHashSet<WebXml>(); for (WebXml fragment : orderedFragments) { resourceJars.add(fragment); } for (WebXml fragment : fragments.values()) { if (!resourceJars.contains(fragment)) { resourceJars.add(fragment); } } processResourceJARs(resourceJars); // See also StandardContext.resourcesStart() for // WEB-INF/classes/META-INF/resources configuration } // Step 11. Apply the ServletContainerInitializer config to the // context if (ok) { for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializerClassMap.entrySet()) { if (entry.getValue().isEmpty()) { context.addServletContainerInitializer( entry.getKey(), null); } else { context.addServletContainerInitializer( entry.getKey(), entry.getValue()); } } } }
你可以看到在這個方法里對web.xml進行了讀取解析,包括Tomcat全局web.xml(在conf目錄下)以及部署在Tomcat里webapps里面WEB-INF下的web.xml。看圖3:
圖3描述的是當StandardContext狀態變為init后通知ContextConfig后,ContextConfig做的一系列事情,可以看出從解析所有的xml到將xml數據存儲在StandardContext里,然后就開始包裝Servlet了,獲取xml里有關Servlet或Jsp的配置,創建Wrapper並設置各種屬性包括ServletClass,如果是Jsp就會先去訪問一次讓其編譯成Servlet然后在設置到Wrapper,在將Wrapper加入StandardContext中。然后在將xml配置里的其他信息如servletMapping也設置到Context容器里。都添加完畢了,要想使用Servlet還要去init Servlet(反射獲取)【loadOnStartup>0的】,也就是去調用Servlet的init方法初始化Servlet。到此從通知ContextConfig到ContextConfig把web.xml解析創建Wrapper,使用InstanceManager反射原理獲取Servlet對象,初始化Servlet並封裝StandWrapper(即Wrapper門面類)都完成了。
這時候觀察者的任務完成,也就是到了圖1的12,13,14已完成。然后在圖1的16,17,18,19是創建一個Connector連接器啟動http服務初始化MapperListener,當socket連接上服務器后,一個Http請求過來被Connector連接到Tomcat,MapperListener被觸發,它會讀取這個Http請求的URL地址,這個MapperListener中含有上下文所有的信息,看圖4:
既然MapperListener含有上下文所有的信息,自然也知道Mapper和Wrapper,自然也能知道這個URL請求的是那個web服務的哪個Servlet。看下圖5,了解請求過程。
看圖6了解從Http請求過來到被MapperListener觸發,獲取URL信息找到映射的Mapping和Wrapper(Servlet)並封裝成MappingData向后傳遞。經過引擎找到Java虛擬機,getHost獲取虛擬主機(簡單理解:虛擬主機是空間 就是我們做網站時候存放網站程序的地方),getContext拿到上下文,找到對應的Wrapper。
在此處插入一段許大佬一段文字和代碼,一段讓我深有感觸的地方:
從上圖7可以看出 Servlet 規范就是基於這幾個類運轉的,與 Servlet 主動關聯的是三個類,分別是 ServletConfig、ServletRequest 和 ServletResponse。這三個類都是通過容器傳遞給 Servlet 的,其中 ServletConfig 是在 Servlet 初始化時就傳給 Servlet 了,而后兩個是在請求達到時調用 Servlet 時傳遞過來的。我們很清楚 ServletRequest 和 ServletResponse 在 Servlet 運行的意義,但是 ServletConfig 和 ServletContext 對 Servlet 有何價值?仔細查看 ServletConfig 接口中聲明的方法發現,這些方法都是為了獲取這個 Servlet 的一些配置屬性,而這些配置屬性可能在 Servlet 運行時被用到。而 ServletContext 又是干什么的呢? Servlet 的運行模式是一個典型的“握手型的交互式”運行模式。所謂“握手型的交互式”就是兩個模塊為了交換數據通常都會准備一個交易場景,這個場景一直跟隨個這個交易過程直到這個交易完成為止。這個交易場景的初始化是根據這次交易對象指定的參數來定制的,這些指定參數通常就會是一個配置類。所以對號入座,交易場景就由 ServletContext 來描述,而定制的參數集合就由 ServletConfig 來描述。而 ServletRequest 和 ServletResponse 就是要交互的具體對象了,它們通常都是作為運輸工具來傳遞交互結果。【自己對比J2EE API查看ServletContext】
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class ServletToJsp extends HttpServlet { private static final long serialVersionUID = 1L; @Override public void doGet (HttpServletRequest request, HttpServletResponse response) { try { // Set the attribute and Forward to hello.jsp request.setAttribute ("servletName", "servletToJsp"); getServletConfig().getServletContext().getRequestDispatcher( "/jsp/jsptoserv/hello.jsp").forward(request, response); } catch (Exception ex) { ex.printStackTrace (); } } }
在理解“場景”的同時,更要把場景和Tomcat體系聯系起來。看圖8.1,Servlet被包裝成StandardWrapper,而StandardWrapperFacade又是StandardWrapper的門面類,這二者都繼承了ServletConfig,在這個"場景"(ServletContext)中使用的不是ServletConfig而是StandardWrapperFacade門面類。在看看ServletContext和StandardContext,一個是提供一個一次交互的場景,一個是上下文環境,在上下文環境里依賴這某一次交互的"場景"。也就是說,當一次交互過來時,在上下文環境中准備一個"場景"即ServletContext,在這個上下文的"場景"中含有着StandardWrapperFacade(一些Servlet的配置和處理邏輯的Servlet),在這個"場景"里交互的對象即是ServletRequest 和 ServletResponse,它們通常都是作為運輸工具來傳遞交互結果。
下圖8.2描述的是request和response在不同模塊中會有不同封裝。我們在service方法中通常使用的是HttpServletRequest和HttpServletResponse,這二者在"場景"中是ServletRequest 和 ServletResponse,由下圖8.2即可知道為什么service方法可以使用HttpServletRequest和HttpServletResponse。
tomcat訪問所有的資源,都是用Servlet來處理的。三種資源划分:靜態資源(js,css,png,jpg),Servlet,JSP。
對於靜態資源交給org.apache.catalina.servlets.DefaultServlet來處理(就是全局web.xml里的servlet),在全局web.xml的default servlet上面有這么一句話:
<!-- The default servlet for all web applications, that serves static --> <!-- resources. It processes all requests that are not mapped to other --> <!-- servlets with servlet mappings (defined either here or in your own --> <!-- web.xml file). --> <!-- 所有的Web應用程序的默認servlet,用於處理靜態資源。--> <!-- 它處理所有未映射到其他帶有servlet映射的servlet(在此處或在您的定義中)。--> <servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>listings</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
從代碼標紅的那句話來看,也就是說處理邏輯最后才是DefaultServlet。
對於JSP,Tomcat最后會交給全局web.xml里的org.apache.jasper.servlet.JspServlet來處理。
<!-- The JSP page compiler and execution servlet, which is the mechanism --> <!-- used by Tomcat to support JSP pages. Traditionally, this servlet --> <!-- is mapped to the URL pattern "*.jsp". This servlet supports the --> <!-- following initialization parameters (default values are in square --> <!-- brackets): --> <!-- JSP頁面編譯器和執行servlet,這是由Tomcat用於支持JSP頁面的機制。 --> <!-- 傳統上,這個servlet映射到URL模式“*.jsp --> <servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>fork</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>xpoweredBy</param-name> <param-value>false</param-value> </init-param> <load-on-startup>3</load-on-startup> </servlet>
對於Servlet,Tomcat最后會交給一個叫做InvokerServlet的類來處理。在tomcat7以前的web.xml里有這么一段被注釋的配置:
<-- The default servlet-invoking servlet for most web applications, --> used to serve requests to servlets that have not been registered in --> <!-- the web application deployment descriptor.--> <!-- 為大多數Web應用程序調用servlet的默認servlet,用於向尚未在Web應用程序 --> <!-- 部署描述符中注冊的servlet提供請求。 --> <!-- <servlet> <servlet-name>invoker</servlet-name> <servlet-class> org.apache.catalina.servlets.InvokerServlet </servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet> --> <!-- <servlet-mapping> <servlet-name>invoker</servlet-name> <url-pattern>/servlet/*</url-pattern> </servlet-mapping> -->
注釋:Web Application Server提供了一種默認的訪問servlet的方式,即通過http://myWebApp/mypackage.MyServlet的方式直接訪問,而不需要定義<servlet>和<servlet-mapping>,這種功能稱為Invoker Servlet,但是現在的App Server一般都默認禁用的這個功能。
從上面的學習中並沒有出現過Invoker Servlet的影子了,對於web.xml中配置的<servlet>和<servlet-mapping>被讀取封裝成wrapper放在容器中,在請求時通過Mapping Data去找到某個Wrapper運行,這都是由StandardContext容器來處理的。在tomcat7及其以后的版本里上面這段代碼都被移除了【在tomcat源碼里已經找不到org.apache.catalina.servlets.InvokerServlet類了】。
打開tomcat源碼org.apache.tomcat.util.http.mapper.Mapper搜索internalMapWrapper方法,即可看到在該方法內定義匹配的七大順序:
Rule 1 -- Exact Match 精確匹配 Servlet
Rule 2 -- Prefix Match 前綴匹配 JSP
Rule 3 -- Extension Match 擴展匹配
Rule 4a -- Welcome resources processing for exact macth
Rule 4b -- Welcome resources processing for prefix match
Rule 4c -- Welcome resources processing for physical folder
Rule 7 -- Default servlet DefaultServlet--靜態資源
可見最后匹配的才是DefaultServlet,也就是說當一個HTTP請求過來后先去找Mapping Data中的path去匹配Servlet的url,沒有才去按規則2匹配下一個,就這樣一直到最后DefaultServlet,如果到最后還沒匹配到怎么辦?返回前台404,資源未找到。
Session 與 Cookie 的作用都是為了保持訪問用戶與后端服務器的交互狀態。它們有各自的優點也有各自的缺陷。然而具有諷刺意味的是它們優點和它們的使用場景又是矛盾的,例如使用 Cookie 來傳遞信息時,隨着 Cookie 個數的增多和訪問量的增加,它占用的網絡帶寬也很大,試想假如 Cookie 占用 200 個字節,如果一天的 PV 有幾億的時候,它要占用多少帶寬。所以大訪問量的時候希望用 Session,但是 Session 的致命弱點是不容易在多台服務器之間共享,所以這也限制了 Session 的使用。
在理解web項目中的Session和cookie時,謹記以下點:
1、Session與Cookie的作用都是為了保持訪問用戶與后台服務器的交互狀態。
2、Session並不是在有客戶端訪問時被創建的,而是在服務器端調用了HttpServletRequest.getSession(true)時才被創建的,如果他訪問的是一個Servlet而且這個Servlet返回的不是Jsp,而是Html或其他格式的頁面,那么就需要request.getSession()才會生成Session,如果Servlet返回的是一個Jsp或者直接訪問的就是一個Jsp,那么你要知道HttpSession是Jsp的內置對象,當這個Jsp被編譯稱Servlet時就已經被創建了。總結來說就是Session不是主動生成的,而是需要后端調用getSession()方法時才生成Session。
基於 Cookie,如果你沒有修改 Context 容器個 cookies 標識的話,默認也是支持的
基於 SSL,默認不支持,只有 connector.getAttribute("SSLEnabled") 為 TRUE 時才支持
當瀏覽器不支持Cookie功能時,瀏覽器會將用戶的SessionCookieName重寫到用戶請求的URL參數中,他的傳遞格式如/path/Servlet;name=value;name2=value2?name3=value3,其中"Servlet;"后面的K-V就,就是要傳遞的Path Parameters,服務器會從這個Path Parameters中拿到用戶配置的SessionCookieName。關於這個SessionCookieName,如果你在web.xml中配置了session-config配置項的話,其 cookie-config 下的 name 屬性就是這個 SessionCookieName 值,如果你沒有配置 session-config 配置項,默認的 SessionCookieName 就是大家熟悉的“JSESSIONID”。接着 Request 根據這個 SessionCookieName 到 Parameters 拿到 Session ID 並設置到 request.setRequestedSessionId 中。
請注意如果客戶端也支持 Cookie 的話,Tomcat 仍然會解析 Cookie 中的 session id,並會覆蓋 URL 中的 Session ID。
如果是第三種情況的話將會根據 javax.servlet.request.ssl_session 屬性值設置 Session ID。
有了SessionId服務器就可以創建HttpSession對象了,第一次觸發是通過request.getSession()方法,如果當前的Session Id還沒有對應的HttpSession對象那么就創建一個新的,並將這個對象添加到org.apache.catalina. Manager 的 sessions 容器中保存,Manager 類將管理所有 Session 的生命周期,Session 過期將被回收,服務器關閉,Session 將被序列化到磁盤等。只要這個 HttpSession 對象存在,用戶就可以根據 Session ID 來獲取到這個對象,也就達到了狀態的保持。
<?xml version='1.0' encoding='utf-8'?> <Server port="8005" shutdown="SHUTDOWN"> <Service name="Catalina"> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8"/> <Engine name="Catalina" defaultHost="localhost"> <Host name="localhost"> <Context path="" docBase="C:\Users\admin\Desktop\ServletDemo\webapp" reloadable="true"/> </Host> </Engine> </Service> </Server>
// web.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <filter> <filter-name>helloFilter</filter-name> <filter-class>demo.HelloFilter</filter-class> </filter> <filter-mapping> <filter-name>helloFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>hello_world</servlet-name> <servlet-class>demo.HelloServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>hello_world</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> </web-app>
package demo; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 最簡單的Servlet * @author Winter Lau */ public class HelloServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.getWriter().println("Hello World!"); } }
package demo; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; public class HelloFilter implements Filter { @Override public void init(FilterConfig arg0) throws ServletException { System.out.println("Filter 初始化"); } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; System.out.println("攔截 URI=" + request.getRequestURI()); chain.doFilter(req, res); } @Override public void destroy() { System.out.println("Filter 結束"); } }
在上面一開始的我們是沒有使用Jsp的,也沒有在service()里使用request.getSession(),編譯一下將生成的class文件覆蓋WEB-INF下demo里的class文件,然后啟動Tomcat
# 編譯命令
javac -encoding utf-8 -classpath C:\Users\admin\Desktop\ServletDemo\src\demo\servlet-api.jar C:\Users\admin\Desktop\ServletDemo\src\demo\*.java
req.getSession();
重新編譯,將編譯后的class文件覆蓋WEB-INF下的class文件,清空瀏覽器緩存,再次啟動Tomcat。
對比以上兩張圖,得出結論,JSESSIONID是服務器調用getSession()才生成的。
要想了解更多,可以看看最開始推薦的那篇關於Cookie的文章。
只需要cd 到jdk的bin目錄下,然后執行下面代碼即可,它會在和Test.java的同目錄下生成Test.calss文件。
javac C:\Users\ServletDemo\src\demo\Test.java
編譯Servlet文件,由於Servlet文件依賴servlet-api.jar包,你沒有這個包,編譯的時候會報錯,因為它不認識Servlet.java里的HttpServletRequest等對象,所以,你要想編譯的話,必須將servlet-api.jar的路徑配置在CLASSPATH的最前面:
,;%TOMCAT_HOME%\lib\servlet-api.jar
Linux下為UTF-8編碼,javac編譯gbk編碼的java文件時,報錯:編碼UTF8的不可映射字符,解決辦法:
javac -encoding gbk ServletTest.java
Windows下為GBK編碼,javac編譯utf-8編碼的java文件時,報錯:編碼GBK的不可映射字符,解決辦法:
javac -encoding utf-8 ServletTest.java
如果沒把servlet-api.jar放在classpath里你也可以這樣寫:
javac -encoding utf-8 -classpath C:\Users\src\demo\servlet-api.jar C:\Users\src\demo\*.java
深析Tomcat工作流程、Servlet深入、Session、cookie