淺析Tomcat工作流程


淺析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?

 

讀紅薯寫Servlet的文章,遇到的問題和探索。

 

底層必讀

讀許令波文

一段簡短的server.xml配置

+自我理解

Tomcat結構

Tomcat工作流程(前半部分)

觀察者ContextConfig做了哪些事

Tomcat工作流程(后半部分)

上下文和“場景”

tomcat對靜態資源是怎么訪問的?

Session和Cookie了解

理解Session和Cookie

測試Session和Cookie

Servlet的編譯命令

編譯普通的java文件

編譯Servlet文件

編譯過程編碼問題

接下來要讀的是

 

 

 

按照大佬給的步驟一頓操作猛如虎,但是自己操作完之后就開始問了自己幾個問題,哪里不會問哪里。

 

底層必讀

 HttpServlet

 ServetConfig

 ServletContext

 Filter

 FilterConfig

 FilterChain

 RequestDispatcher

 HttpServletRequest

 HttpServletResponse

 HttpSession

 一些 Listenser 類

 

讀許令波文

 

一段簡短的server.xml配置

<?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屬性中設定。

Context元素的屬性:

 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。

 

 

+自我理解

 

Tomcat結構

1.Tomcat結構理解

 

 

 

1讓我理解了Tomcat整體的結構,在整個Tomcat中,被容器的概念貫穿,四層結構Engine,Host,Context,Weapper,從頂層Engine開始,到Host,再到Context,最后是Wrapper,每一層都是一個容器。每一個Context都代表着一個單獨的web項目。在Context中為了解耦,將Servlet封裝在Tomcat自身的Wrapper容器里,使得Tomcat與Servlet的關聯不在緊密。

 

 

 

 

 

Tomcat工作流程(前半部分)

2.從啟動到完成的整個工作流程

 

 

 

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

 

觀察者ContextConfig做了哪些事

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.觀察者做的一系列事情

 

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門面類)都完成了。

 

Tomcat工作流程(后半部分)

這時候觀察者的任務完成,也就是到了圖1的12,13,14已完成。然后在圖1的16,17,18,19是創建一個Connector連接器啟動http服務初始化MapperListener,當socket連接上服務器后,一個Http請求過來被Connector連接到Tomcat,MapperListener被觸發,它會讀取這個Http請求的URL地址,這個MapperListener中含有上下文所有的信息,看圖4:

4.MapperListener為什么會有上下文所有信息

 

 

 

既然MapperListener含有上下文所有的信息,自然也知道Mapper和Wrapper,自然也能知道這個URL請求的是那個web服務的哪個Servlet。看下圖5,了解請求過程。

 

5

 

 

 

 

看圖6了解從Http請求過來到被MapperListener觸發,獲取URL信息找到映射的Mapping和Wrapper(Servlet)並封裝成MappingData向后傳遞。經過引擎找到Java虛擬機,getHost獲取虛擬主機(簡單理解:虛擬主機是空間 就是我們做網站時候存放網站程序的地方),getContext拿到上下文,找到對應的Wrapper。

6.映射URL到交給Servlet處理業務

 

 

 

上下文和“場景”

在此處插入一段許大佬一段文字和代碼,一段讓我深有感觸的地方:

 

7.Servlet 頂層類關聯圖

 

 

 

從上圖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.Servlet體系和Tomcat體系的系列圖

8.1

 

 

下圖8.2描述的是request和response在不同模塊中會有不同封裝。我們在service方法中通常使用的是HttpServletRequest和HttpServletResponse,這二者在"場景"中是ServletRequest 和 ServletResponse,由下圖8.2即可知道為什么service方法可以使用HttpServletRequest和HttpServletResponse。

 

8.2

 

 

tomcat對靜態資源是怎么訪問的?

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類了】。

 

 

 

 

Mapper對資源調用的七大規則

打開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了解

 

理解Session和Cookie

 

 

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。

3、Session是基於Cookie工作的。

有三種方式可以讓Session正常工作:

 基於 URL Path Parameter,默認就支持

 基於 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 來獲取到這個對象,也就達到了狀態的保持。

 

測試Session和Cookie

 先配置一下初始環境,Tomcat下的server.xml

<?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>

 

 項目結構

-- ServletDemo

-- src

-- demo

-- HelloFilter.java

-- HelloServlet.java

-- servlet-api.jar

-- webapp

-- WEB-INF

-- classes

-- demo

-- HelloFilter.class

-- HelloServlet.calss

-- web.xml

 項目基本文件

// 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

 

 

 

 修改service()方法,在其內部添加

req.getSession();

重新編譯,將編譯后的class文件覆蓋WEB-INF下的class文件,清空瀏覽器緩存,再次啟動Tomcat。

 

 

 

對比以上兩張圖,得出結論,JSESSIONID是服務器調用getSession()才生成的。

要想了解更多,可以看看最開始推薦的那篇關於Cookie的文章。

 

 

最后附上一張Servlet中的Listener圖:

 

 

 

Servlet的編譯命令

 

編譯普通的java文件

只需要cd 到jdk的bin目錄下,然后執行下面代碼即可,它會在和Test.java的同目錄下生成Test.calss文件。

javac C:\Users\ServletDemo\src\demo\Test.java

 

編譯Servlet文件

編譯Servlet文件,由於Servlet文件依賴servlet-api.jar包,你沒有這個包,編譯的時候會報錯,因為它不認識Servlet.java里的HttpServletRequest等對象,所以,你要想編譯的話,必須將servlet-api.jar的路徑配置在CLASSPATH的最前面:

,;%TOMCAT_HOME%\lib\servlet-api.jar

然后才能想編譯普通文件那樣編譯Servlet文件

 

編譯過程編碼問題

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM