Servlet 教程——檢視閱讀

卡斯特梅的雨傘發表於2020-05-04

Servlet 教程——檢視閱讀

參考

Servlet教程——菜鳥——藍本

Servlet教程——w3cschool

Servlet教程——易百

servlet依賴maven依賴:

    <!--servlet依賴-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>
<!--    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.1</version>
      <scope>provided</scope>
    </dependency>-->

略讀

Servlet 為建立基於 web 的應用程式提供了基於元件、獨立於平臺的方法,可以不受 CGI 程式的效能限制。Servlet 有許可權訪問所有的 Java API,包括訪問企業級資料庫的 JDBC API。

Servlet 是什麼?

Java Servlet 是執行在 Web 伺服器或應用伺服器上的程式,它是作為來自 Web 瀏覽器或其他 HTTP 客戶端的請求和 HTTP 伺服器上的資料庫或應用程式之間的中間層。

使用 Servlet,您可以收集來自網頁表單的使用者輸入,呈現來自資料庫或者其他源的記錄,還可以動態建立網頁。

Java Servlet 通常情況下與使用 CGI(Common Gateway Interface,公共閘道器介面)實現的程式可以達到異曲同工的效果。但是相比於 CGI,Servlet 有以下幾點優勢:

  • 效能明顯更好。
  • Servlet 在 Web 伺服器的地址空間內執行。這樣它就沒有必要再建立一個單獨的程式來處理每個客戶端請求。
  • Servlet 是獨立於平臺的,因為它們是用 Java 編寫的。
  • 伺服器上的 Java 安全管理器執行了一系列限制,以保護伺服器計算機上的資源。因此,Servlet 是可信的。
  • Java 類庫的全部功能對 Servlet 來說都是可用的。它可以通過 sockets 和 RMI 機制與 applets、資料庫或其他軟體進行互動。

Servlet 架構

下圖顯示了 Servlet 在 Web 應用程式中的位置。

Servlet 生命週期

Servlet 生命週期可被定義為從建立直到毀滅的整個過程。以下是 Servlet 遵循的過程:

  • Servlet 通過呼叫 init () 方法進行初始化。
  • Servlet 呼叫 service() 方法來處理客戶端的請求。
  • Servlet 通過呼叫 destroy() 方法終止(結束)。
  • 最後,Servlet 是由 JVM 的垃圾回收器進行垃圾回收的。

Servlet 建立於使用者第一次呼叫對應於該 Servlet 的 URL 時,但是您也可以指定 Servlet 在伺服器第一次啟動時被載入。

當使用者呼叫一個 Servlet 時,就會建立一個 Servlet 例項(注意,這個例項是單例的,所有的請求都是在這個裡面處理,HttpServletRequest才是原型的,每次請求會新建一個例項),每一個使用者請求都會產生一個新的執行緒,適當的時候移交給 doGet 或 doPost 方法。

架構圖

下圖顯示了一個典型的 Servlet 生命週期方案。

  • 第一個到達伺服器的 HTTP 請求被委派到 Servlet 容器。
  • Servlet 容器在呼叫 service() 方法之前載入 Servlet。
  • 然後 Servlet 容器處理由多個執行緒產生的多個請求,每個執行緒執行一個單一的 Servlet 例項的 service() 方法。

Servlet 處理表單資料,這些資料會根據不同的情況使用不同的方法自動解析:

  • getParameter():您可以呼叫 request.getParameter() 方法來獲取表單引數的值。
  • getParameterValues():如果引數出現一次以上,則呼叫該方法,並返回多個值,例如核取方塊。
  • getParameterNames():如果您想要得到當前請求中的所有引數的完整列表,則呼叫該方法。

Servlet 簡介

Servlet 是什麼?

Java Servlet 是執行在 Web 伺服器或應用伺服器上的程式,它是作為來自 Web 瀏覽器或其他 HTTP 客戶端的請求和 HTTP 伺服器上的資料庫或應用程式之間的中間層。

使用 Servlet,您可以收集來自網頁表單的使用者輸入,呈現來自資料庫或者其他源的記錄,還可以動態建立網頁。

Java Servlet 通常情況下與使用 CGI(Common Gateway Interface,公共閘道器介面)實現的程式可以達到異曲同工的效果。但是相比於 CGI,Servlet 有以下幾點優勢:

  • 效能明顯更好。
  • Servlet 在 Web 伺服器的地址空間內執行。這樣它就沒有必要再建立一個單獨的程式來處理每個客戶端請求。
  • Servlet 是獨立於平臺的,因為它們是用 Java 編寫的。
  • 伺服器上的 Java 安全管理器執行了一系列限制,以保護伺服器計算機上的資源。因此,Servlet 是可信的。
  • Java 類庫的全部功能對 Servlet 來說都是可用的。它可以通過 sockets 和 RMI 機制與 applets、資料庫或其他軟體進行互動。

Servlet 架構

Servlet 包

Java Servlet 是執行在帶有支援 Java Servlet 規範的直譯器的 web 伺服器上的 Java 類。

Servlet 可以使用 javax.servlet 和 javax.servlet.http 包建立,它是 Java 企業版的標準組成部分,Java 企業版是支援大型開發專案的 Java 類庫的擴充套件版本。

這些類實現 Java Servlet 和 JSP 規範。在寫本教程的時候,二者相應的版本分別是 Java Servlet 2.5 和 JSP 2.1。

Java Servlet 就像任何其他的 Java 類一樣已經被建立和編譯。在您安裝 Servlet 包並把它們新增到您的計算機上的 Classpath 類路徑中之後,您就可以通過 JDK 的 Java 編譯器或任何其他編譯器來編譯 Servlet。

Servlet 建立的三種方式。

對於一個 Servlet 類,我們日常最常用的方法是繼承自 HttpServlet 類,提供了 Http 相關的方法,HttpServlet 擴充套件了 GenericServlet 類,而 GenericServlet 類又實現了 Servlet 類和 ServletConfig 類。

Servlet 類提供了五個方法,其中三個生命週期方法和兩個普通方法 。 一個Servlet只會有一個物件,服務所有的請求(單例)。

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

1、實現 Servlet 介面

//Servlet的生命週期:從Servlet被建立到Servlet被銷燬的過程
//一次建立,到處服務
//一個Servlet只會有一個物件,服務所有的請求
/*
 * 1.例項化(使用構造方法建立物件)
 * 2.初始化  執行init方法
 * 3.服務     執行service方法
 * 4.銷燬    執行destroy方法
 */
public class ServletDemo1 implements Servlet {

    //public ServletDemo1(){}

     //生命週期方法:當Servlet第一次被建立物件時執行該方法,該方法在整個生命週期中只執行一次
    public void init(ServletConfig arg0) throws ServletException {
                System.out.println("=======init=========");
        }

    //生命週期方法:對客戶端響應的方法,該方法會被執行多次,每次請求該servlet都會執行該方法
    public void service(ServletRequest arg0, ServletResponse arg1)
            throws ServletException, IOException {
        System.out.println("hehe");

    }

    //生命週期方法:當Servlet被銷燬時執行該方法
    public void destroy() {
        System.out.println("******destroy**********");
    }
//當停止tomcat時也就銷燬的servlet。
    public ServletConfig getServletConfig() {

        return null;
    }

    public String getServletInfo() {

        return null;
    }
}

2、繼承 GenericServlet 類

它實現了 Servlet 介面,重寫 service 的方法,不過這種方法我們極少用。

GenericServlet 是一個抽象類,實現了 Servlet 介面,並且對其中的 init() 和 destroy() 和 service() 提供了預設實現。在 GenericServlet 中,主要完成了以下任務:

  • 將 init() 中的 ServletConfig 賦給一個類級變數,可以由 getServletConfig 獲得;

  • 為 Servlet 所有方法提供預設實現;

  • 可以直接呼叫 ServletConfig 中的方法;

    abstract class GenericServlet implements Servlet,ServletConfig{

     //GenericServlet通過將ServletConfig賦給類級變數
     private trServletConfig servletConfig;
    
     public void init(ServletConfig servletConfig) throws ServletException {
    
        this.servletConfig=servletConfig;
    
        /*自定義init()的原因是:如果子類要初始化必須覆蓋父類的init() 而使它無效 這樣
         this.servletConfig=servletConfig不起作用 這樣就會導致空指標異常 這樣如果子類要初始化,
         可以直接覆蓋不帶引數的init()方法 */
        this.init();
     }
     
     //自定義的init()方法,可以由子類覆蓋  
     //init()不是生命週期方法
     public void init(){
    
     }
    
     //實現service()空方法,並且宣告為抽象方法,強制子類必須實現service()方法 
     public abstract void service(ServletRequest request,ServletResponse response) 
       throws ServletException,java.io.IOException{
     }
    
     //實現空的destroy方法
     public void destroy(){ }
    

    }

3、繼承 HttpServlet 方法

HttpServlet 也是一個抽象類,它進一步繼承並封裝了 GenericServlet,使得使用更加簡單方便,由於是擴充套件了 Http 的內容,所以還需要使用 HttpServletRequest 和 HttpServletResponse,這兩個類分別是 ServletRequest 和 ServletResponse 的子類

HttpServlet 中對原始的 Servlet 中的方法都進行了預設的操作,不需要顯式的銷燬初始化以及 service(),在 HttpServlet 中,自定義了一個新的 service() 方法,其中通過 getMethod() 方法判斷請求的型別,從而呼叫 doGet() 或者 doPost() 處理 get,post 請求,使用者只需要繼承 HttpServlet,然後重寫 doPost() 或者 doGet() 方法處理請求即可。

我們一般都使用繼承 HttpServlet 的方式來定義一個 servlet。

注意:

service() 是執行緒安全的。

Servlet 容器處理由多個執行緒產生的多個請求,每個執行緒執行一個單一的 Servlet 例項的 service() 方法 。這種多執行緒是執行緒安全的,因為我們的service(HttpServletRequest req, HttpServletResponse resp)方法傳入的引數

HttpServletRequest req, HttpServletResponse resp都是自己的,雖然只有一個例項,但service方法裡面沒有呼叫到類成員變數等共享資源,就不會有執行緒安全的問題;而在service方法裡,雖然只有一個例項,我們假設兩個請求同時到該方法中,也是沒有問題的,因為區域性變數是執行緒安全的,每個請求呼叫service方法都會開闢一個棧空間儲存區域性變數,是不會串位的,這是由於方法直接是執行緒隔離的,不要擔心同時請求導致變數串了。

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getMethod();
    long lastModified;
    if (method.equals("GET")) {
        lastModified = this.getLastModified(req);
        if (lastModified == -1L) {
            this.doGet(req, resp);
        } else {
            long ifModifiedSince = req.getDateHeader("If-Modified-Since");
            if (ifModifiedSince < lastModified) {
                this.maybeSetLastModified(resp, lastModified);
                this.doGet(req, resp);
            } else {
                resp.setStatus(304);
            }
        }
    } else if (method.equals("HEAD")) {
        lastModified = this.getLastModified(req);
        this.maybeSetLastModified(resp, lastModified);
        this.doHead(req, resp);
    } else if (method.equals("POST")) {
        this.doPost(req, resp);
    } else if (method.equals("PUT")) {
        this.doPut(req, resp);
    } else if (method.equals("DELETE")) {
        this.doDelete(req, resp);
    } else if (method.equals("OPTIONS")) {
        this.doOptions(req, resp);
    } else if (method.equals("TRACE")) {
        this.doTrace(req, resp);
    } else {
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[]{method};
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(501, errMsg);
    }

}

Servlet 生命週期

Servlet 生命週期可被定義為從建立直到毀滅的整個過程。以下是 Servlet 遵循的過程:

  • Servlet 通過呼叫 init () 方法進行初始化。
  • Servlet 呼叫 service() 方法來處理客戶端的請求。
  • Servlet 通過呼叫 destroy() 方法終止(結束)。
  • 最後,Servlet 是由 JVM 的垃圾回收器進行垃圾回收的。

init() 方法

init 方法被設計成只呼叫一次。它在第一次建立 Servlet 時被呼叫,在後續每次使用者請求時不再呼叫。

Servlet 建立於使用者第一次呼叫對應於該 Servlet 的 URL 時,但是您也可以指定 Servlet 在伺服器第一次啟動時被載入。

當使用者呼叫一個 Servlet 時,就會建立一個 Servlet 例項,每一個使用者請求都會產生一個新的執行緒,適當的時候移交給 doGet 或 doPost 方法。init() 方法簡單地建立或載入一些資料,這些資料將被用於 Servlet 的整個生命週期。

service() 方法

service() 方法是執行實際任務的主要方法。Servlet 容器(即 Web 伺服器)呼叫 service() 方法來處理來自客戶端(瀏覽器)的請求,並把格式化的響應寫回給客戶端。

每次伺服器接收到一個 Servlet 請求時,伺服器會產生一個新的執行緒並呼叫服務。service() 方法檢查 HTTP 請求型別(GET、POST、PUT、DELETE 等),並在適當的時候呼叫 doGet、doPost、doPut,doDelete 等方法。

service() 方法由容器呼叫,service 方法在適當的時候呼叫 doGet、doPost、doPut、doDelete 等方法。所以,您不用對 service() 方法做任何動作,您只需要根據來自客戶端的請求型別來重寫 doGet() 或 doPost() 即可。

doGet() 方法

GET 請求來自於一個 URL 的正常請求,或者來自於一個未指定 METHOD 的 HTML 表單,它由 doGet() 方法處理。

doPost() 方法

POST 請求來自於一個特別指定了 METHOD 為 POST 的 HTML 表單,它由 doPost() 方法處理。

destroy() 方法

destroy() 方法只會被呼叫一次,在 Servlet 生命週期結束時被呼叫。destroy() 方法可以讓您的 Servlet 關閉資料庫連線、停止後臺執行緒、把 Cookie 列表或點選計數器寫入到磁碟,並執行其他類似的清理活動。

Servlet生命週期架構圖

下圖顯示了一個典型的 Servlet 生命週期方案。

  • 第一個到達伺服器的 HTTP 請求被委派到 Servlet 容器。
  • Servlet 容器在呼叫 service() 方法之前載入 Servlet。
  • 然後 Servlet 容器處理由多個執行緒產生的多個請求,每個執行緒執行一個單一的 Servlet 例項的 service() 方法。

Servlet 例項

Servlet 是服務 HTTP 請求並實現 javax.servlet.Servlet 介面的 Java 類。Web 應用程式開發人員通常編寫 Servlet 來擴充套件 javax.servlet.http.HttpServlet,並實現 Servlet 介面的抽象類專門用來處理 HTTP 請求。

報錯:路徑重名的話tomcat啟動不了。

servlet 瀏覽器訪問路徑配置有個小問題:

1、java 類裡的註解 —— @WebServlet("/HelloServlet") 對應瀏覽器路徑:

http://localhost:8080/TomcatTest/HelloServlet

2、配置檔案(web.xml)裡對應的瀏覽器訪問路徑:

http://localhost:8080/TomcatTest/HelloServlet

這兩種配一個就好了,不然路徑重名的話反而會讓tomcat啟動不了。

例如這樣就啟動不了:

修改 web.xml :

<url-pattern>/HelloServlet</url-pattern>

修改後,web.xml 和 java 類的註解,對應路徑都是:

http://localhost:8080/TomcatTest/HelloServlet

導致

命名的 servlet[HelloServlet]和 [com.runoob.test.HelloServlet] 都被對映到 URL 模式 [/ HelloServlet] 這是不允許的。

解決辦法:

將註解去掉或者保留註解進入web.xml將對映刪除既可以。

示例:

//@WebServlet("/HelloWorld")
public class HelloWorld extends HttpServlet {

    private String message;

    @Override
    public void init() throws ServletException
    {
        // 執行必需的初始化
        message = "Hello World";
    }
    @Override
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
            throws ServletException, IOException
    {
        // 設定響應內容型別
        response.setContentType("text/html");

        // 實際的邏輯是在這裡
        PrintWriter out = response.getWriter();
        out.println("<h1>" + message.toUpperCase() + "</h1>");
    }
    @Override
    public void destroy()
    {
        // 什麼也不做
    }
}

web.xml

<servlet>
  <servlet-name>HelloWorld</servlet-name>
  <servlet-class>com.jsptest.HelloWorld</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>HelloWorld</servlet-name>
  <url-pattern>/HelloWorld</url-pattern>
</servlet-mapping>

Servlet 表單資料

很多情況下,需要傳遞一些資訊,從瀏覽器到 Web 伺服器,最終到後臺程式。瀏覽器使用兩種方法可將這些資訊傳遞到 Web 伺服器,分別為 GET 方法和 POST 方法。

GET 方法

GET 方法向頁面請求傳送已編碼的使用者資訊。頁面和已編碼的資訊中間用 ? 字元分隔,如下所示:

http://www.test.com/hello?key1=value1&key2=value2

GET 方法是預設的從瀏覽器向 Web 伺服器傳遞資訊的方法,它會產生一個很長的字串,出現在瀏覽器的位址列中。如果您要向伺服器傳遞的是密碼或其他的敏感資訊,請不要使用 GET 方法。GET 方法有大小限制:請求字串中最多隻能有 1024 個字元。

這些資訊使用 QUERY_STRING 頭傳遞,並可以通過 QUERY_STRING 環境變數訪問,Servlet 使用 doGet() 方法處理這種型別的請求。

POST 方法

另一個向後臺程式傳遞資訊的比較可靠的方法是 POST 方法。POST 方法打包資訊的方式與 GET 方法基本相同,但是 POST 方法不是把資訊作為 URL 中 ? 字元後的文字字串進行傳送,而是把這些資訊作為一個單獨的訊息。訊息以標準輸出的形式傳到後臺程式,您可以解析和使用這些標準輸出。Servlet 使用 doPost() 方法處理這種型別的請求。

使用 Servlet 讀取表單資料

Servlet 處理表單資料,這些資料會根據不同的情況使用不同的方法自動解析:

  • getParameter():您可以呼叫 request.getParameter() 方法來獲取表單引數的值。
  • getParameterValues():如果引數出現一次以上,則呼叫該方法,並返回多個值,例如核取方塊。
  • getParameterNames():如果您想要得到當前請求中的所有引數的完整列表,則呼叫該方法。

示例:

hellomybatis-servlet.xml 響應字元編碼設定為utf-8

<!-- 訊息轉換器程式碼,處理請求返回json字串的中文亂碼問題 -->
<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="supportedMediaTypes">
                <list>
                    <value>text/html;charset=UTF-8</value>
                    <!--<value>application/json;charset=UTF-8</value>-->
                </list>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

web.xml 請求字元編碼過濾為utf-8

<!-- 字元編碼過濾器 -->
<filter>
  <filter-name>encodingFilter</filter-name>
  <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  <init-param>
    <param-name>encoding</param-name>
    <param-value>utf-8</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>encodingFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

//加了@WebServlet 註解就不用在web.xml配置servlet
@WebServlet("/show")
public class ShowServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        String title = "GET 獲取稱號";
        //不需要轉譯編碼的原因是
        //String name = new String(req.getParameter("name").getBytes("ISO-8859-1"), "UTF-8");
        //String titleName = new String(req.getParameter("title").getBytes("ISO-8859-1"), "UTF-8");
        String docType = "<!DOCTYPE html> \n";
        writer.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + title + "</h1>\n" +
                "<ul>\n" +
                "  <li><b>人物</b>:"
                + req.getParameter("name") + "\n" +
                "  <li><b>稱號</b>:"
                + req.getParameter("title") + "\n" +
                "</ul>\n" +
                "</body></html>");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

輸出:

表單提交get示例:

web.xml

  <servlet>
    <servlet-name>hellomybatis</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>
        WEB-INF/hellomybatis-servlet.xml
      </param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>hellomybatis</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.xml</url-pattern>
  </servlet-mapping>
  <!--如果是*.html則不走代理,走預設請求到html檔案上-->
  <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.html</url-pattern>
  </servlet-mapping>

show.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>傭兵天下</title>
</head>
<body>
<form action="/hellomybatis/show" method="GET">
    人物:<input type="text" name="name">
    <br />
    稱號:<input type="text" name="title" />
    <input type="submit" value="提交" />
</form>
</body>
</html>

ShowServlet 不變。

使用表單的 POST 方法例項:

只需要把請求改成post,後臺對應重寫doPost()方法即可。

 <form action="/hellomybatis/show" method="POST">
    人物:<input type="text" name="name">
    <br />
    稱號:<input type="text" name="title" />
    <input type="submit" value="提交" />
</form>

Servlet 客戶端 HTTP 請求

當瀏覽器請求網頁時,它會向 Web 伺服器傳送特定資訊,這些資訊不能被直接讀取,因為這些資訊是作為 HTTP 請求的頭的一部分進行傳輸的。您可以檢視 HTTP 協議 瞭解更多相關資訊。

以下是來自於瀏覽器端的重要頭資訊,您可以在 Web 程式設計中頻繁使用:

頭資訊 描述
Accept 這個頭資訊指定瀏覽器或其他客戶端可以處理的 MIME 型別。值 image/png 或 image/jpeg 是最常見的兩種可能值。
Accept-Charset 這個頭資訊指定瀏覽器可以用來顯示資訊的字符集。例如 ISO-8859-1。
Accept-Encoding 這個頭資訊指定瀏覽器知道如何處理的編碼型別。值 gzip 或 compress 是最常見的兩種可能值。
Accept-Language 這個頭資訊指定客戶端的首選語言,在這種情況下,Servlet 會產生多種語言的結果。例如,en、en-us、ru 等。
Authorization 這個頭資訊用於客戶端在訪問受密碼保護的網頁時識別自己的身份。
Connection 這個頭資訊指示客戶端是否可以處理持久 HTTP 連線。持久連線允許客戶端或其他瀏覽器通過單個請求來檢索多個檔案。值 Keep-Alive 意味著使用了持續連線。
Content-Length 這個頭資訊只適用於 POST 請求,並給出 POST 資料的大小(以位元組為單位)。
Cookie 這個頭資訊把之前傳送到瀏覽器的 cookies 返回到伺服器。
Host 這個頭資訊指定原始的 URL 中的主機和埠。
If-Modified-Since 這個頭資訊表示只有當頁面在指定的日期後已更改時,客戶端想要的頁面。如果沒有新的結果可以使用,伺服器會傳送一個 304 程式碼,表示 Not Modified 頭資訊。
If-Unmodified-Since 這個頭資訊是 If-Modified-Since 的對立面,它指定只有當文件早於指定日期時,操作才會成功。
Referer 這個頭資訊指示所指向的 Web 頁的 URL。例如,如果您在網頁 1,點選一個連結到網頁 2,當瀏覽器請求網頁 2 時,網頁 1 的 URL 就會包含在 Referer 頭資訊中。
User-Agent 這個頭資訊識別發出請求的瀏覽器或其他客戶端,並可以向不同型別的瀏覽器返回不同的內容。

讀取 HTTP 頭的方法

下面的方法可用在 Servlet 程式中讀取 HTTP 頭。這些方法通過 HttpServletRequest 物件可用。

序號 方法 & 描述
1 Cookie[] getCookies() 返回一個陣列,包含客戶端傳送該請求的所有的 Cookie 物件。
2 Enumeration getAttributeNames() 返回一個列舉,包含提供給該請求可用的屬性名稱。
3 Enumeration getHeaderNames() 返回一個列舉,包含在該請求中包含的所有的頭名。
4 Enumeration getParameterNames() 返回一個 String 物件的列舉,包含在該請求中包含的引數的名稱。
5 HttpSession getSession() 返回與該請求關聯的當前 session 會話,或者如果請求沒有 session 會話,則建立一個。
6 HttpSession getSession(boolean create) 返回與該請求關聯的當前 HttpSession,或者如果沒有當前會話,且建立是真的,則返回一個新的 session 會話。
7 Locale getLocale() 基於 Accept-Language 頭,返回客戶端接受內容的首選的區域設定。
8 Object getAttribute(String name) 以物件形式返回已命名屬性的值,如果沒有給定名稱的屬性存在,則返回 null。
9 ServletInputStream getInputStream() 使用 ServletInputStream,以二進位制資料形式檢索請求的主體。
10 String getAuthType() 返回用於保護 Servlet 的身份驗證方案的名稱,例如,"BASIC" 或 "SSL",如果JSP沒有受到保護則返回 null。
11 String getCharacterEncoding() 返回請求主體中使用的字元編碼的名稱。
12 String getContentType() 返回請求主體的 MIME 型別,如果不知道型別則返回 null。
13 String getContextPath() 返回指示請求上下文的請求 URI 部分。
14 String getHeader(String name) 以字串形式返回指定的請求頭的值。
15 String getMethod() 返回請求的 HTTP 方法的名稱,例如,GET、POST 或 PUT。
16 String getParameter(String name) 以字串形式返回請求引數的值,或者如果引數不存在則返回 null。
17 String getPathInfo() 當請求發出時,返回與客戶端傳送的 URL 相關的任何額外的路徑資訊。
18 String getProtocol() 返回請求協議的名稱和版本。
19 String getQueryString() 返回包含在路徑後的請求 URL 中的查詢字串。
20 String getRemoteAddr() 返回傳送請求的客戶端的網際網路協議(IP)地址。
21 String getRemoteHost() 返回傳送請求的客戶端的完全限定名稱。
22 String getRemoteUser() 如果使用者已通過身份驗證,則返回發出請求的登入使用者,或者如果使用者未通過身份驗證,則返回 null。
23 String getRequestURI() 從協議名稱直到 HTTP 請求的第一行的查詢字串中,返回該請求的 URL 的一部分。
24 String getRequestedSessionId() 返回由客戶端指定的 session 會話 ID。
25 String getServletPath() 返回撥用 JSP 的請求的 URL 的一部分。
26 String[] getParameterValues(String name) 返回一個字串物件的陣列,包含所有給定的請求引數的值,如果引數不存在則返回 null。
27 boolean isSecure() 返回一個布林值,指示請求是否使用安全通道,如 HTTPS。
28 int getContentLength() 以位元組為單位返回請求主體的長度,並提供輸入流,或者如果長度未知則返回 -1。
29 int getIntHeader(String name) 返回指定的請求頭的值為一個 int 值。
30 int getServerPort() 返回接收到這個請求的埠號。
31 int getParameterMap() 將引數封裝成 Map 型別。

示例:

@WebServlet("/displayHeaderServlet")
public class DisplayHeaderServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    // 處理 GET 方法請求的方法
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        // 設定響應內容型別
        //如果不設定會導致亂碼
        //HTTP Header ???? - ??????
        response.setContentType("text/html;charset=UTF-8");

        PrintWriter out = response.getWriter();
        String title = "HTTP Header 請求響應頭";
        String docType =
                "<!DOCTYPE html> \n";
        out.println(docType +
                "<html>\n" +
                "<head><meta charset=\"utf-8\"><title>" + title + "</title></head>\n"+
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + title + "</h1>\n" +
                "<table width=\"100%\" border=\"1\" align=\"center\">\n" +
                "<tr bgcolor=\"#949494\">\n" +
                "<th>Header 名稱</th><th>Header 值</th>\n"+
                "</tr>\n");

        Enumeration headerNames = request.getHeaderNames();

        while(headerNames.hasMoreElements()) {
            String paramName = (String)headerNames.nextElement();
            out.print("<tr><td>" + paramName + "</td>\n");
            String paramValue = request.getHeader(paramName);
            out.println("<td> " + paramValue + "</td></tr>\n");
        }
        out.println("</table>\n</body></html>");
    }
    // 處理 POST 方法請求的方法
    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

輸出:

報錯:

1、Class com.jsptest.DisplayHeaderServlet is not a Servlet

這是因為我們在給DisplayHeaderServlet加上@WebServlet時卻沒有繼承HttpServlet,導致系統初始化Servlet時因為DisplayHeaderServlet沒有初始化init()和destroy()等預設方法無法初始化而報錯。

javax.servlet.ServletException: Class com.jsptest.DisplayHeaderServlet is not a Servlet
org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:522)

2、Failed to start component

這是因為@WebServlet("displayHeaderServlet")註解沒有加/ 斜槓導致的servlet不能載入。

Caused by: java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/hellomybatis]]
	at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:730)

@WebServlet("displayHeaderServlet")
public class DisplayHeaderServlet extends HttpServlet {
    /......
}

//正確寫法
@WebServlet("/displayHeaderServlet")
public class DisplayHeaderServlet extends HttpServlet {
    /......
}

Servlet 伺服器 HTTP 響應

狀態行包括 HTTP 版本(在本例中為 HTTP/1.1)、一個狀態碼(在本例中為 200)和一個對應於狀態碼的短訊息(在本例中為 OK)。

下表總結了從 Web 伺服器端返回到瀏覽器的最有用的 HTTP 1.1 響應報頭,您會在 Web 程式設計中頻繁地使用它們:

頭資訊 描述
Allow 這個頭資訊指定伺服器支援的請求方法(GET、POST 等)。
Cache-Control 這個頭資訊指定響應文件在何種情況下可以安全地快取。可能的值有:public、private 或 no-cache 等。Public 意味著文件是可快取,Private 意味著文件是單個使用者私用文件,且只能儲存在私有(非共享)快取中,no-cache 意味著文件不應被快取。
Connection 這個頭資訊指示瀏覽器是否使用持久 HTTP 連線。值 close 指示瀏覽器不使用持久 HTTP 連線,值 keep-alive 意味著使用持久連線。
Content-Disposition 這個頭資訊可以讓您請求瀏覽器要求使用者以給定名稱的檔案把響應儲存到磁碟。
Content-Encoding 在傳輸過程中,這個頭資訊指定頁面的編碼方式。
Content-Language 這個頭資訊表示文件編寫所使用的語言。例如,en、en-us、ru 等。
Content-Length 這個頭資訊指示響應中的位元組數。只有當瀏覽器使用持久(keep-alive)HTTP 連線時才需要這些資訊。
Content-Type 這個頭資訊提供了響應文件的 MIME(Multipurpose Internet Mail Extension)型別。
Expires 這個頭資訊指定內容過期的時間,在這之後內容不再被快取。
Last-Modified 這個頭資訊指示文件的最後修改時間。然後,客戶端可以快取檔案,並在以後的請求中通過 If-Modified-Since 請求頭資訊提供一個日期。
Location 這個頭資訊應被包含在所有的帶有狀態碼的響應中。在 300s 內,這會通知瀏覽器文件的地址。瀏覽器會自動重新連線到這個位置,並獲取新的文件。
Refresh 這個頭資訊指定瀏覽器應該如何儘快請求更新的頁面。您可以指定頁面重新整理的秒數。
Retry-After 這個頭資訊可以與 503(Service Unavailable 服務不可用)響應配合使用,這會告訴客戶端多久就可以重複它的請求。
Set-Cookie 這個頭資訊指定一個與頁面關聯的 cookie。

設定 HTTP 響應報頭的方法

下面的方法可用於在 Servlet 程式中設定 HTTP 響應報頭。這些方法通過 HttpServletResponse 物件可用。

序號 方法 & 描述
1 String encodeRedirectURL(String url) 為 sendRedirect 方法中使用的指定的 URL 進行編碼,或者如果編碼不是必需的,則返回 URL 未改變。
2 String encodeURL(String url) 對包含 session 會話 ID 的指定 URL 進行編碼,或者如果編碼不是必需的,則返回 URL 未改變。
3 boolean containsHeader(String name) 返回一個布林值,指示是否已經設定已命名的響應報頭。
4 boolean isCommitted() 返回一個布林值,指示響應是否已經提交。
5 void addCookie(Cookie cookie) 把指定的 cookie 新增到響應。
6 void addDateHeader(String name, long date) 新增一個帶有給定的名稱和日期值的響應報頭。
7 void addHeader(String name, String value) 新增一個帶有給定的名稱和值的響應報頭。
8 void addIntHeader(String name, int value) 新增一個帶有給定的名稱和整數值的響應報頭。
9 void flushBuffer() 強制任何在緩衝區中的內容被寫入到客戶端。
10 void reset() 清除緩衝區中存在的任何資料,包括狀態碼和頭。
11 void resetBuffer() 清除響應中基礎緩衝區的內容,不清除狀態碼和頭。
12 void sendError(int sc) 使用指定的狀態碼傳送錯誤響應到客戶端,並清除緩衝區。
13 void sendError(int sc, String msg) 使用指定的狀態傳送錯誤響應到客戶端。
14 void sendRedirect(String location) 使用指定的重定向位置 URL 傳送臨時重定向響應到客戶端。
15 void setBufferSize(int size) 為響應主體設定首選的緩衝區大小。
16 void setCharacterEncoding(String charset) 設定被髮送到客戶端的響應的字元編碼(MIME 字符集)例如,UTF-8。
17 void setContentLength(int len) 設定在 HTTP Servlet 響應中的內容主體的長度,該方法設定 HTTP Content-Length 頭。
18 void setContentType(String type) 如果響應還未被提交,設定被髮送到客戶端的響應的內容型別。
19 void setDateHeader(String name, long date) 設定一個帶有給定的名稱和日期值的響應報頭。
20 void setHeader(String name, String value) 設定一個帶有給定的名稱和值的響應報頭。
21 void setIntHeader(String name, int value) 設定一個帶有給定的名稱和整數值的響應報頭。
22 void setLocale(Locale loc) 如果響應還未被提交,設定響應的區域。
23 void setStatus(int sc) 為該響應設定狀態碼。

示例:

@WebServlet("/refresh")
public class RefreshServlet extends HttpServlet {
    // 處理 GET 方法請求的方法
    @Override
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
            throws ServletException, IOException
    {
        // 設定重新整理自動載入時間為 3秒
        response.setIntHeader("Refresh", 3);
        // 設定響應內容型別
        response.setContentType("text/html;charset=UTF-8");
        Cookie cookie = new Cookie("winner","amy");
        response.addCookie(cookie);
        //使用預設時區和語言環境獲得一個日曆
        Calendar cale = Calendar.getInstance();
        //將Calendar型別轉換成Date型別
        Date tasktime=cale.getTime();
        //設定日期輸出的格式
        SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //格式化輸出
        String nowTime = df.format(tasktime);
        PrintWriter out = response.getWriter();
        String title = "自動重新整理 Header 頁面";
        String docType =
                "<!DOCTYPE html>\n";
        out.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n"+
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + title + "</h1>\n" +
                "<p>當前時間是:" + nowTime + "</p>\n");
    }
    // 處理 POST 方法請求的方法
    @Override
    public void doPost(HttpServletRequest request,
                       HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

輸出:

擴充套件:Cookie是跟著session會話走的,我們請求的連結如果加了Cookie,就是對整個會話加Cookie,後面的請求都會帶上這個Cookie。檢視Cookie的兩種方式。一個從network,一個從application.

Servlet HTTP 狀態碼

狀態行包括 HTTP 版本(在本例中為 HTTP/1.1)、一個狀態碼(在本例中為 200)和一個對應於狀態碼的短訊息(在本例中為 OK)。

以下是可能從 Web 伺服器返回的 HTTP 狀態碼和相關的資訊列表:

程式碼 訊息 描述
100 Continue 只有請求的一部分已經被伺服器接收,但只要它沒有被拒絕,客戶端應繼續該請求。
101 Switching Protocols 伺服器切換協議。
200 OK 請求成功。
201 Created 該請求是完整的,並建立一個新的資源。
202 Accepted 該請求被接受處理,但是該處理是不完整的。
203 Non-authoritative Information
204 No Content
205 Reset Content
206 Partial Content
300 Multiple Choices 連結列表。使用者可以選擇一個連結,進入到該位置。最多五個地址。
301 Moved Permanently 所請求的頁面已經轉移到一個新的 URL。
302 Found 所請求的頁面已經臨時轉移到一個新的 URL。
303 See Other 所請求的頁面可以在另一個不同的 URL 下被找到。
304 Not Modified
305 Use Proxy
306 Unused 在以前的版本中使用該程式碼。現在已不再使用它,但程式碼仍被保留。
307 Temporary Redirect 所請求的頁面已經臨時轉移到一個新的 URL。
400 Bad Request 伺服器不理解請求。
401 Unauthorized 所請求的頁面需要使用者名稱和密碼。
402 Payment Required 您還不能使用該程式碼。
403 Forbidden 禁止訪問所請求的頁面。
404 Not Found 伺服器無法找到所請求的頁面。.
405 Method Not Allowed 在請求中指定的方法是不允許的。
406 Not Acceptable 伺服器只生成一個不被客戶端接受的響應。
407 Proxy Authentication Required 在請求送達之前,您必須使用代理伺服器的驗證。
408 Request Timeout 請求需要的時間比伺服器能夠等待的時間長,超時。
409 Conflict 請求因為衝突無法完成。
410 Gone 所請求的頁面不再可用。
411 Length Required "Content-Length" 未定義。伺服器無法處理客戶端傳送的不帶 Content-Length 的請求資訊。
412 Precondition Failed 請求中給出的先決條件被伺服器評估為 false。
413 Request Entity Too Large 伺服器不接受該請求,因為請求實體過大。
414 Request-url Too Long 伺服器不接受該請求,因為 URL 太長。當您轉換一個 "post" 請求為一個帶有長的查詢資訊的 "get" 請求時發生。
415 Unsupported Media Type 伺服器不接受該請求,因為媒體型別不被支援。
417 Expectation Failed
500 Internal Server Error 未完成的請求。伺服器遇到了一個意外的情況。
501 Not Implemented 未完成的請求。伺服器不支援所需的功能。
502 Bad Gateway 未完成的請求。伺服器從上游伺服器收到無效響應。
503 Service Unavailable 未完成的請求。伺服器暫時超載或當機。
504 Gateway Timeout 閘道器超時。
505 HTTP Version Not Supported 伺服器不支援"HTTP協議"版本。

設定 HTTP 狀態程式碼的方法

下面的方法可用於在 Servlet 程式中設定 HTTP 狀態碼。這些方法通過 HttpServletResponse 物件可用。

序號 方法 & 描述
1 public void setStatus ( int statusCode ) 該方法設定一個任意的狀態碼。setStatus 方法接受一個 int(狀態碼)作為引數。如果您的響應包含了一個特殊的狀態碼和文件,請確保在使用 PrintWriter 實際返回任何內容之前呼叫 setStatus。
2 public void sendRedirect(String url) 該方法生成一個 302 響應,連同一個帶有新文件 URL 的 Location 頭。
3 public void sendError(int code, String message) 該方法傳送一個狀態碼(通常為 404),連同一個在 HTML 文件內部自動格式化併傳送到客戶端的短訊息。

示例:

@WebServlet("/error")
public class ErrorServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setStatus(403);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       doGet(req,resp);
    }
}

輸出:

Servlet 編寫過濾器

Servlet 過濾器可以動態地攔截請求和響應,以變換或使用包含在請求或響應中的資訊。

filter物件只會建立一次,init方法也只會執行一次。

可以將一個或多個 Servlet 過濾器附加到一個 Servlet 或一組 Servlet。Servlet 過濾器也可以附加到 JavaServer Pages (JSP) 檔案和 HTML 頁面。呼叫 Servlet 前呼叫所有附加的 Servlet 過濾器。

Servlet 過濾器是可用於 Servlet 程式設計的 Java 類,可以實現以下目的:

  • 在客戶端的請求訪問後端資源之前,攔截這些請求。
  • 在伺服器的響應傳送回客戶端之前,處理這些響應。

根據規範建議的各種型別的過濾器:

  • 身份驗證過濾器(Authentication Filters)。
  • 資料壓縮過濾器(Data compression Filters)。
  • 加密過濾器(Encryption Filters)。
  • 觸發資源訪問事件過濾器。
  • 影像轉換過濾器(Image Conversion Filters)。
  • 日誌記錄和稽核過濾器(Logging and Auditing Filters)。
  • MIME-TYPE 鏈過濾器(MIME-TYPE Chain Filters)。
  • 標記化過濾器(Tokenizing Filters)。
  • XSL/T 過濾器(XSL/T Filters),轉換 XML 內容。

過濾器通過 Web 部署描述符(web.xml)中的 XML 標籤來宣告,然後對映到您的應用程式的部署描述符中的 Servlet 名稱或 URL 模式。

當 Web 容器啟動 Web 應用程式時,它會為您在部署描述符中宣告的每一個過濾器建立一個例項。

Filter的執行順序與在web.xml配置檔案中的配置順序一致,一般把Filter配置在所有的Servlet之前。

Servlet 過濾器方法

過濾器是一個實現了 javax.servlet.Filter 介面的 Java 類。javax.servlet.Filter 介面定義了三個方法:

序號 方法 & 描述
1 public void doFilter (ServletRequest, ServletResponse, FilterChain) 該方法完成實際的過濾操作,當客戶端請求方法與過濾器設定匹配的URL時,Servlet容器將先呼叫過濾器的doFilter方法。FilterChain使用者訪問後續過濾器。
2 public void init(FilterConfig filterConfig) web 應用程式啟動時,web 伺服器將建立Filter 的例項物件,並呼叫其init方法,讀取web.xml配置,完成物件的初始化功能,從而為後續的使用者請求作好攔截的準備工作(filter物件只會建立一次,init方法也只會執行一次)。開發人員通過init方法的引數,可獲得代表當前filter配置資訊的FilterConfig物件。
3 public void destroy() Servlet容器在銷燬過濾器例項前呼叫該方法,在該方法中釋放Servlet過濾器佔用的資源。

示例:

public class LogFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String before = filterConfig.getInitParameter("before");
        // 輸出初始化引數
        System.out.println("初始化前:" + before);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("過濾時執行引數輸出:"+ JSON.toJSONString(servletRequest.getParameterMap()));
        // 把請求傳回過濾鏈
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("關閉 LogFilter");
    }
}

web.xml

<filter>
  <filter-name>LogFilter</filter-name>
  <filter-class>com.filter.LogFilter</filter-class>
  <init-param>
    <param-name>before</param-name>
    <param-value>日誌列印</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>LogFilter</filter-name>
  <!--過濾器適用於所有的 Servlet,因為我們在配置中指定 /* ,url-pattern用於匹配我們想要過濾的請求路徑-->
  <url-pattern>/*</url-pattern>
</filter-mapping>

請求:<http://localhost:8080/hellomybatis/show?name=amy&title=屠龍勇士

輸出:

使用多個過濾器

Web 應用程式可以根據特定的目的定義若干個不同的過濾器。假設您定義了兩個過濾器 AuthenFilter 和 LogFilter。

專案啟動時會初始化filter過濾器,初始化是隨機的,不一定以web.xml配置的一樣。使用多個過濾器以在web.xml配置的先後順序執行。

示例:

web.xml

<filter>
  <filter-name>LogFilter</filter-name>
  <filter-class>com.filter.LogFilter</filter-class>
  <init-param>
    <param-name>before</param-name>
    <param-value>日誌列印</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>LogFilter</filter-name>
  <!--過濾器適用於所有的 Servlet,因為我們在配置中指定 /* ,url-pattern用於匹配我們想要過濾的請求路徑-->
  <url-pattern>/*</url-pattern>
</filter-mapping>

<filter>
  <filter-name>AuthenFilter</filter-name>
  <filter-class>com.filter.AuthenFilter</filter-class>
  <init-param>
    <param-name>authen</param-name>
    <param-value>許可權校驗</param-value>
  </init-param>
</filter>
  <filter-mapping>
    <filter-name>AuthenFilter</filter-name>
    <!--過濾器適用於所有的 Servlet,因為我們在配置中指定 /* ,url-pattern用於匹配我們想要過濾的請求路徑-->
    <!--<url-pattern>/*</url-pattern>-->
    <!--只適用於@WebServlet("/show")-->
    <url-pattern>/show</url-pattern>
    <!--不能進入-->
    <!--<url-pattern>/showyyy</url-pattern>-->
    <!--也可以用<servlet-name>指定過濾器所攔截的Servlet名稱,不過該名字必須配置在web.xml中,不能用註解,否則不能識別-->
    <!--<servlet-name></servlet-name>-->
  </filter-mapping>

public class AuthenFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String authen = filterConfig.getInitParameter("authen");
        // 輸出初始化引數
        System.out.println("初始化前:" + authen);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //獲取請求資訊(測試時可以通過get方式在URL中新增name)
        String name = servletRequest.getParameter("name");

        // 過濾器核心程式碼邏輯
        System.out.println("過濾器獲取請求引數:"+name);
        System.out.println("AuthenFilter 過濾");

        if("amy".equals(name)){
            // 把請求傳回過濾鏈
            filterChain.doFilter(servletRequest, servletResponse);
        }else{
            //設定返回內容型別
            servletResponse.setContentType("text/html;charset=UTF-8");

            //在頁面輸出響應資訊
            PrintWriter out = servletResponse.getWriter();
            out.print("<b>:"+name+"不正確,請求被攔截,不能訪問web資源</b>");
            System.out.println("name:"+name+"不正確,請求被攔截,不能訪問web資源");
        }
    }

    @Override
    public void destroy() {
        System.out.println("關閉 LogFilter");
    }
}

請求:<http://localhost:8080/hellomybatis/show?name=amy&title=屠龍勇士

請求:<http://localhost:8080/hellomybatis/show?name=happ&title=屠龍勇士

輸出:

初始化前:許可權校驗
初始化前:日誌列印
過濾時執行引數輸出:{"name":["amy"],"title":["屠龍勇士"]}
過濾器獲取請求引數:amy
AuthenFilter 過濾
過濾時執行引數輸出:{"name":["happ"],"title":["屠龍勇士"]}
過濾器獲取請求引數:happ
AuthenFilter 過濾
name:happ不正確,請求被攔截,不能訪問web資源

過濾器的應用順序

web.xml 中的 filter-mapping 元素的順序決定了 Web 容器應用過濾器到 Servlet 的順序。若要反轉過濾器的順序,您只需要在 web.xml 檔案中反轉 filter-mapping 元素即可

web.xml配置各節點說明

<filter>指定一個過濾器。
<filter-name>用於為過濾器指定一個名字,該元素的內容不能為空。
<filter-class>元素用於指定過濾器的完整的限定類名。
<init-param>元素用於為過濾器指定初始化引數,它的子元素<param-name>指定引數的名字,<param-value>指定引數的值。
在過濾器中,可以使用FilterConfig介面物件來訪問初始化引數。
<filter-mapping>元素用於設定一個 Filter 所負責攔截的資源。一個Filter攔截的資源可通過兩種方式來指定:Servlet 名稱和資源訪問的請求路徑
<filter-name>子元素用於設定filter的註冊名稱。該值必須是在<filter>元素中宣告過的過濾器的名字
<url-pattern>設定 filter 所攔截的請求路徑(過濾器關聯的URL樣式)
<servlet-name>指定過濾器所攔截的Servlet名稱。
<dispatcher>指定過濾器所攔截的資源被 Servlet 容器呼叫的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,預設REQUEST。使用者可以設定多個<dispatcher>子元素用來指定 Filter 對資源的多種呼叫方式進行攔截。
<dispatcher>子元素可以設定的值及其意義
REQUEST:當使用者直接訪問頁面時,Web容器將會呼叫過濾器。如果目標資源是通過RequestDispatcher的include()或forward()方法訪問時,那麼該過濾器就不會被呼叫。
INCLUDE:如果目標資源是通過RequestDispatcher的include()方法訪問時,那麼該過濾器將被呼叫。除此之外,該過濾器不會被呼叫。
FORWARD:如果目標資源是通過RequestDispatcher的forward()方法訪問時,那麼該過濾器將被呼叫,除此之外,該過濾器不會被呼叫。
ERROR:如果目標資源是通過宣告式異常處理機制呼叫時,那麼該過濾器將被呼叫。除此之外,過濾器不會被呼叫。

Servlet 異常處理

當一個 Servlet 丟擲一個異常時,Web 容器在使用了 exception-type 元素的 web.xml 中搜尋與丟擲異常型別相匹配的配置。

您必須在 web.xml 中使用 error-page 元素來指定對特定異常 或 HTTP 狀態碼 作出相應的 Servlet 呼叫。

請求屬性 - 錯誤/異常

以下是錯誤處理的 Servlet 可以訪問的請求屬性列表,用來分析錯誤/異常的性質。

序號 屬性 & 描述
1 javax.servlet.error.status_code 該屬性給出狀態碼,狀態碼可被儲存,並在儲存為 java.lang.Integer 資料型別後可被分析。
2 javax.servlet.error.exception_type 該屬性給出異常型別的資訊,異常型別可被儲存,並在儲存為 java.lang.Class 資料型別後可被分析。
3 javax.servlet.error.message 該屬性給出確切錯誤訊息的資訊,資訊可被儲存,並在儲存為 java.lang.String 資料型別後可被分析。
4 javax.servlet.error.request_uri 該屬性給出有關 URL 呼叫 Servlet 的資訊,資訊可被儲存,並在儲存為 java.lang.String 資料型別後可被分析。
5 javax.servlet.error.exception 該屬性給出異常產生的資訊,資訊可被儲存,並在儲存為 java.lang.Throwable 資料型別後可被分析。
6 javax.servlet.error.servlet_name 該屬性給出 Servlet 的名稱,名稱可被儲存,並在儲存為 java.lang.String 資料型別後可被分析。

示例:

web.xml

<!-- error-code 相關的錯誤頁面 -->
<error-page>
  <error-code>404</error-code>
  <location>/errorHandler</location>
</error-page>
<!-- exception-type 相關的錯誤頁面 -->
<error-page>
  <!--<exception-type>javax.servlet.ServletException</exception-type>-->
  <exception-type>java.lang.Throwable</exception-type >
  <location>/errorHandler</location>
</error-page>


@WebServlet("/errorHandler")
public class ErrorHandlerServlet extends HttpServlet {

    // 處理 GET 方法請求的方法
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        Throwable throwable = (Throwable)
                request.getAttribute("javax.servlet.error.exception");
        Integer statusCode = (Integer)
                request.getAttribute("javax.servlet.error.status_code");
        String servletName = (String)
                request.getAttribute("javax.servlet.error.servlet_name");
        if (servletName == null){
            servletName = "Unknown";
        }
        String requestUri = (String)
                request.getAttribute("javax.servlet.error.request_uri");
        if (requestUri == null){
            requestUri = "Unknown";
        }
        // 設定響應內容型別
        response.setContentType("text/html;charset=UTF-8");

        PrintWriter out = response.getWriter();
        String title = "異常頁面 Error/Exception 資訊";

        String docType = "<!DOCTYPE html>\n";
        out.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n");
        out.println("<h1>異常資訊展示</h1>");
        if (throwable == null && statusCode == null){
            out.println("<h2>錯誤資訊丟失</h2>");
            out.println("請返回 <a href=\"" +
                    response.encodeURL("http://localhost:8080/") +
                    "\">主頁</a>。");
        }else if (statusCode != null) {
            out.println("錯誤程式碼 : " + statusCode);
        }else{
            out.println("<h2>錯誤資訊</h2>");
            out.println("Servlet Name : " + servletName +
                    "</br></br>");
            out.println("異常型別 : " +
                    throwable.getClass( ).getName( ) +
                    "</br></br>");
            out.println("請求 URI: " + requestUri +
                    "<br><br>");
            out.println("異常資訊: " +
                    throwable.getMessage( ));
        }
        out.println("</body>");
        out.println("</html>");
    }
    // 處理 POST 方法請求的方法
    @Override
    public void doPost(HttpServletRequest request,
                       HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }
}

錯誤請求:http://localhost:8080/hellomybatis/df

輸出:

Servlet Cookie 處理

使用cookie的三個步驟:

  • 伺服器指令碼向瀏覽器傳送一組 Cookie。例如:姓名、年齡或識別號碼等。

  • 瀏覽器將這些資訊儲存在本地計算機上,以備將來使用。

  • 當下一次瀏覽器向 Web 伺服器傳送任何請求時,瀏覽器會把這些 Cookie 資訊傳送到伺服器,伺服器將使用這些資訊來識別使用者。

    //Servlet Cookie 處理需要對中文進行編碼與解碼,方法如下:
    String str = java.net.URLEncoder.encode("中文","UTF-8"); //編碼
    String str = java.net.URLDecoder.decode("編碼後的字串","UTF-8"); // 解碼

Cookie 剖析

Cookie 通常設定在 HTTP 頭資訊中.

正如您所看到的,Set-Cookie 頭包含了一個名稱值對、一個 GMT 日期、一個路徑和一個域。名稱和值會被 URL 編碼。expires 欄位是一個指令,告訴瀏覽器在給定的時間和日期之後"忘記"該 Cookie。

如果瀏覽器被配置為儲存 Cookie,它將會保留此資訊直到到期日期。如果使用者的瀏覽器指向任何匹配該 Cookie 的路徑和域的頁面,它會重新傳送 Cookie 到伺服器。瀏覽器的頭資訊可能如下所示:

Servlet Cookie 方法

以下是在 Servlet 中操作 Cookie 時可使用的有用的方法列表。

序號 方法 & 描述
1 public void setDomain(String pattern) 該方法設定 cookie 適用的域,例如 runoob.com。
2 public String getDomain() 該方法獲取 cookie 適用的域,例如 runoob.com。
3 public void setMaxAge(int expiry) 該方法設定 cookie 過期的時間(以秒為單位)。如果不這樣設定,cookie 只會在當前 session 會話中持續有效。
4 public int getMaxAge() 該方法返回 cookie 的最大生存週期(以秒為單位),預設情況下,-1 表示 cookie 將持續下去,直到瀏覽器關閉。
5 public String getName() 該方法返回 cookie 的名稱。名稱在建立後不能改變。
6 public void setValue(String newValue) 該方法設定與 cookie 關聯的值。
7 public String getValue() 該方法獲取與 cookie 關聯的值。
8 public void setPath(String uri) 該方法設定 cookie 適用的路徑。如果您不指定路徑,與當前頁面相同目錄下的(包括子目錄下的)所有 URL 都會返回 cookie。
9 public String getPath() 該方法獲取 cookie 適用的路徑。
10 public void setSecure(boolean flag) 該方法設定布林值,表示 cookie 是否應該只在加密的(即 SSL)連線上傳送。
11 public void setComment(String purpose) 設定cookie的註釋。該註釋在瀏覽器向使用者呈現 cookie 時非常有用。
12 public String getComment() 獲取 cookie 的註釋,如果 cookie 沒有註釋則返回 null。

通過 Servlet 設定 Cookie

通過 Servlet 設定 Cookie 包括三個步驟:

(1) 建立一個 Cookie 物件:您可以呼叫帶有 cookie 名稱和 cookie 值的 Cookie 建構函式,cookie 名稱和 cookie 值都是字串。

Cookie cookie = new Cookie("key","value");

請記住,無論是名字還是值,都不應該包含空格或以下任何字元:

[ ] ( ) = , " / ? @ : ;

(2) 設定最大生存週期:您可以使用 setMaxAge 方法來指定 cookie 能夠保持有效的時間(以秒為單位)。下面將設定一個最長有效期為 24 小時的 cookie。

cookie.setMaxAge(60*60*24); 

(3) 傳送 Cookie 到 HTTP 響應頭:您可以使用 response.addCookie 來新增 HTTP 響應頭中的 Cookie,如下所示:

response.addCookie(cookie);

通過 Servlet 讀取 Cookie

要讀取 Cookie,您需要通過呼叫 HttpServletRequest 的 getCookies( ) 方法建立一個 javax.servlet.http.Cookie 物件的陣列。然後迴圈遍歷陣列,並使用 getName() 和 getValue() 方法來訪問每個 cookie 和關聯的值。

通過 Servlet 刪除 Cookie

刪除 Cookie 是非常簡單的。如果您想刪除一個 cookie,那麼您只需要按照以下三個步驟進行:

  • 讀取一個現有的 cookie,並把它儲存在 Cookie 物件中。
  • 使用 setMaxAge() 方法設定 cookie 的年齡為零,來刪除現有的 cookie。
  • 把這個 cookie 新增到響應頭。

例項:

新增cookie:

@WebServlet("/show")
public class ShowServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 為名字和姓氏建立 Cookie
        Cookie name = new Cookie("name",
                URLEncoder.encode(req.getParameter("name"), "UTF-8")); // 中文轉碼
        Cookie title = new Cookie("title",
                req.getParameter("title"));

        // 為兩個 Cookie 設定過期日期為 24 小時後
        name.setMaxAge(60*60*24);
        title.setMaxAge(60*60*24);

        // 在響應頭中新增兩個 Cookie
        resp.addCookie( name );
        resp.addCookie( title );

        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        String top = "GET 獲取稱號";
        //不需要轉譯編碼的原因是
        //String name = new String(req.getParameter("name").getBytes("ISO-8859-1"), "UTF-8");
        //String titleName = new String(req.getParameter("title").getBytes("ISO-8859-1"), "UTF-8");
        String docType = "<!DOCTYPE html> \n";
        writer.println(docType +
                "<html>\n" +
                "<head><title>" + top + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + top + "</h1>\n" +
                "<ul>\n" +
                "  <li><b>人物</b>:"
                + req.getParameter("name") + "\n" +
                "  <li><b>稱號</b>:"
                + req.getParameter("title") + "\n" +
                "</ul>\n" +
                "</body></html>");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

@WebServlet("/readCookies")
public class ReadCookiesServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Cookie cookie = null;
        Cookie[] cookies = null;
        // 獲取與該域相關的 Cookie 的陣列
        cookies = request.getCookies();

        // 設定響應內容型別
        response.setContentType("text/html;charset=UTF-8");

        PrintWriter out = response.getWriter();
        String title = "Get Cookie Example";
        String docType = "<!DOCTYPE html>\n";
        out.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n");
        if (cookies != null) {
            out.println("<h2>Cookie 名稱和值</h2>");
            for (int i = 0; i < cookies.length; i++) {
                cookie = cookies[i];
                if((cookie.getName( )).compareTo("name") == 0 ){
                    cookie.setMaxAge(0);
                    response.addCookie(cookie);
                    out.print("已刪除的 cookie:" +
                            cookie.getName( ) + "<br/>");
                }
                out.print("名稱:" + cookie.getName() + ",");
                out.print("值:" + URLDecoder.decode(cookie.getValue(), "utf-8") + " <br/>");
            }
        } else {
            out.println(
                    "<h2 class=\"tutheader\">No Cookie founds</h2>");
        }
        out.println("</body>");
        out.println("</html>");
    }


    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

}

請求:

<http://localhost:8080/hellomybatis/show?name=amy&title=屠龍勇士

http://localhost:8080/hellomybatis/readCookies

輸出:

Servlet Session 跟蹤

HTTP 是一種"無狀態"協議,這意味著每次客戶端檢索網頁時,客戶端開啟一個單獨的連線到 Web 伺服器,伺服器會自動不保留之前客戶端請求的任何記錄。

但是仍然有以下4種方式來維持 Web 客戶端和 Web 伺服器之間的 session 會話:

  • Cookies
  • 隱藏的表單欄位
  • URL 重寫
  • HttpSession 物件

Cookies

一個 Web 伺服器可以分配一個唯一的 session 會話 ID 作為每個 Web 客戶端的 cookie,對於客戶端的後續請求可以使用接收到的 cookie 來識別。

缺點:

這可能不是一個有效的方法,因為很多瀏覽器不支援 cookie,所以我們建議不要使用這種方式來維持 session 會話。

隱藏的表單欄位

一個 Web 伺服器可以傳送一個隱藏的 HTML 表單欄位,以及一個唯一的 session 會話 ID,如下所示:

<input type="hidden" name="sessionid" value="12345">

該條目意味著,當表單被提交時,指定的名稱和值會被自動包含在 GET 或 POST 資料中。每次當 Web 瀏覽器傳送回請求時,session_id 值可以用於保持不同的 Web 瀏覽器的跟蹤。

缺點:

這可能是一種保持 session 會話跟蹤的有效方式,但是點選常規的超文字連結()不會導致表單提交,因此隱藏的表單欄位也不支援常規的 session 會話跟蹤。

URL 重寫

您可以在每個 URL 末尾追加一些額外的資料來標識 session 會話,伺服器會把該 session 會話識別符號與已儲存的有關 session 會話的資料相關聯。

例如,http://w3cschool.cc/file.htm;sessionid=12345,session 會話識別符號被附加為 sessionid=12345,識別符號可被 Web 伺服器訪問以識別客戶端。

URL 重寫是一種更好的維持 session 會話的方式,它在瀏覽器不支援 cookie 時能夠很好地工作,但是它的缺點是會動態生成每個 URL 來為頁面分配一個 session 會話 ID,即使是在很簡單的靜態 HTML 頁面中也會如此。

HttpSession 物件

Servlet 還提供了 HttpSession 介面,該介面提供了一種跨多個頁面請求或訪問網站時識別使用者以及儲存有關使用者資訊的方式。

Servlet 容器使用這個介面來建立一個 HTTP 客戶端和 HTTP 伺服器之間的 session 會話。會話持續一個指定的時間段,跨多個連線或頁面請求。

您會通過呼叫 HttpServletRequest 的公共方法 getSession() 來獲取 HttpSession 物件,如下所示:

HttpSession session = request.getSession();

你需要在向客戶端傳送任何文件內容之前呼叫 request.getSession()。下面總結了 HttpSession 物件中可用的幾個重要的方法:

序號 方法 & 描述
1 public Object getAttribute(String name) 該方法返回在該 session 會話中具有指定名稱的物件,如果沒有指定名稱的物件,則返回 null。
2 public Enumeration getAttributeNames() 該方法返回 String 物件的列舉,String 物件包含所有繫結到該 session 會話的物件的名稱。
3 public long getCreationTime() 該方法返回該 session 會話被建立的時間,自格林尼治標準時間 1970 年 1 月 1 日午夜算起,以毫秒為單位。
4 public String getId() 該方法返回一個包含分配給該 session 會話的唯一識別符號的字串。
5 public long getLastAccessedTime() 該方法返回客戶端最後一次傳送與該 session 會話相關的請求的時間自格林尼治標準時間 1970 年 1 月 1 日午夜算起,以毫秒為單位。
6 public int getMaxInactiveInterval() 該方法返回 Servlet 容器在客戶端訪問時保持 session 會話開啟的最大時間間隔,以秒為單位。
7 public void invalidate() 該方法指示該 session 會話無效,並解除繫結到它上面的任何物件。
8 public boolean isNew() 如果客戶端還不知道該 session 會話,或者如果客戶選擇不參入該 session 會話,則該方法返回 true。
9 public void removeAttribute(String name) 該方法將從該 session 會話移除指定名稱的物件。
10 public void setAttribute(String name, Object value) 該方法使用指定的名稱繫結一個物件到該 session 會話。
11 public void setMaxInactiveInterval(int interval) 該方法在 Servlet 容器指示該 session 會話無效之前,指定客戶端請求之間的時間,以秒為單位。

刪除 Session 會話資料

當您完成了一個使用者的 session 會話資料,您有以下幾種選擇:

  • 移除一個特定的屬性:您可以呼叫 public void removeAttribute(String name) 方法來刪除與特定的鍵相關聯的值。

  • 刪除整個 session 會話:您可以呼叫 public void invalidate() 方法來丟棄整個 session 會話。

  • 設定 session 會話過期時間:您可以呼叫 public void setMaxInactiveInterval(int interval) 方法來單獨設定 session 會話超時。

  • 登出使用者:如果使用的是支援 servlet 2.4 的伺服器,您可以呼叫 logout 來登出 Web 伺服器的客戶端,並把屬於所有使用者的所有 session 會話設定為無效。

  • web.xml 配置:如果您使用的是 Tomcat,除了上述方法,您還可以在 web.xml 檔案中配置 session 會話超時,如下所示:

    <session-config>
      <session-timeout>15</session-timeout>
    </session-config>
    

上面例項中的超時時間是以分鐘為單位,將覆蓋 Tomcat 中預設的 30 分鐘超時時間。

在一個 Servlet 中的 getMaxInactiveInterval() 方法會返回 session 會話的超時時間,以秒為單位。所以,如果在 web.xml 中配置 session 會話超時時間為 15 分鐘,那麼 getMaxInactiveInterval() 會返回 900。

例項:需要用新的tomcat,否則計數沒出來。老的tomcat session儲存在redis中了。

@WebServlet("/sessionTrack")
public class SessionTrackServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        // 如果不存在 session 會話,則建立一個 session 物件
        HttpSession session = request.getSession(true);
        // 獲取 session 建立時間
        Date createTime = new Date(session.getCreationTime());
        // 獲取該網頁的最後一次訪問時間
        Date lastAccessTime = new Date(session.getLastAccessedTime());

        //設定日期輸出的格式
        SimpleDateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        String title = "Servlet Session 例項 - 菜鳥教程";
        Integer visitCount = new Integer(0);
        String visitCountKey = new String("visitCount");
        String userIDKey = new String("userID");
        String userID = new String("Runoob");
        if(session.getAttribute(visitCountKey) == null) {
            session.setAttribute(visitCountKey, new Integer(0));
        }


        // 檢查網頁上是否有新的訪問者
        if (session.isNew()){
            title = "Servlet Session 例項 - 菜鳥教程";
            session.setAttribute(userIDKey, userID);
        } else {
            visitCount = (Integer)session.getAttribute(visitCountKey);
            visitCount = visitCount + 1;
            userID = (String)session.getAttribute(userIDKey);
        }
        session.setAttribute(visitCountKey,  visitCount);

        // 設定響應內容型別
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        String docType = "<!DOCTYPE html>\n";
        out.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + title + "</h1>\n" +
                "<h2 align=\"center\">Session 資訊</h2>\n" +
                "<table border=\"1\" align=\"center\">\n" +
                "<tr bgcolor=\"#949494\">\n" +
                "  <th>Session 資訊</th><th>值</th></tr>\n" +
                "<tr>\n" +
                "  <td>id</td>\n" +
                "  <td>" + session.getId() + "</td></tr>\n" +
                "<tr>\n" +
                "  <td>建立時間</td>\n" +
                "  <td>" +  df.format(createTime) +
                "  </td></tr>\n" +
                "<tr>\n" +
                "  <td>最後訪問時間</td>\n" +
                "  <td>" + df.format(lastAccessTime) +
                "  </td></tr>\n" +
                "<tr>\n" +
                "  <td>使用者 ID</td>\n" +
                "  <td>" + userID +
                "  </td></tr>\n" +
                "<tr>\n" +
                "  <td>訪問統計:</td>\n" +
                "  <td>" + visitCount + "</td></tr>\n" +
                "</table>\n" +
                "</body></html>");
    }
}

Servlet 資料庫訪問

例項:

@WebServlet("/databaseAccess")
public class DatabaseAccessServlet extends HttpServlet {

    private static final String USER_NAME = "root";

    private static final String PASSWORD = "123456";

    // JDBC 驅動名及資料庫 URL
    private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    private static final String DB_URL = "jdbc:mysql://localhost:3306/hello_mybatis";


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Connection connection = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        // 設定響應內容型別
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        String title = "Servlet Mysql Test";
        String docType = "<!DOCTYPE html>\n";
        out.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + title + "</h1>\n");
        try {
            Class.forName(JDBC_DRIVER);
            connection = DriverManager.getConnection(DB_URL, USER_NAME, PASSWORD);
            pstmt = connection.prepareStatement("SELECT * FROM t_user WHERE 1=1 AND NAME = ? ");
            if (StringUtils.isNoneEmpty(req.getParameter("name"))){
                pstmt.setString(1, req.getParameter("name"));
            }
            rs = pstmt.executeQuery();
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                String dept = rs.getString("dept");
                String phone = rs.getString("phone");
                // 輸出資料
                out.println("ID: " + id);
                out.println(", 英雄: " + name);
                out.println(", 所屬帝國: " + dept);
                out.println(", 聯絡方式: " + phone);
                out.println("<br />");
            }
            out.println("</body></html>");

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                rs.close();
                pstmt.close();
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       doGet(req, resp);
    }
}

請求:http://localhost:8080/hellomybatis/databaseAccess?name=池寒楓

輸出:

Servlet 檔案上傳

Servlet 可以與 HTML form 標籤一起使用,來允許使用者上傳檔案到伺服器。上傳的檔案可以是文字檔案或影像檔案或任何文件。

需要引入的 jar 檔案:commons-fileupload-1.3.2、commons-io-2.5.jar。

建立一個檔案上傳表單

下面的 HTML 程式碼建立了一個檔案上傳表單。以下幾點需要注意:

  • 表單 method 屬性應該設定為 POST 方法,不能使用 GET 方法。
  • 表單 enctype 屬性應該設定為 multipart/form-data.
  • 表單 action 屬性應該設定為在後端伺服器上處理檔案上傳的 Servlet 檔案。下面的例項使用了 UploadServlet Servlet 來上傳檔案。
  • 上傳單個檔案,您應該使用單個帶有屬性 type="file" 的 <input .../> 標籤。為了允許多個檔案上傳,請包含多個 name 屬性值不同的 input 標籤。輸入標籤具有不同的名稱屬性的值。瀏覽器會為每個 input 標籤關聯一個瀏覽按鈕。

示例:

pom.xml

<!--檔案上傳依賴,Servlet3.0 已經內建了檔案上傳這一特性,開發者不再需要將 Commons FileUpload 元件匯入到工程中去。-->
    <dependency>
      <groupId>com.asiainfo.crm.apache</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.1</version>
    </dependency>
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.6</version>
    </dependency>

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>檔案上傳例項</title>
</head>
<body>
<h1>檔案上傳例項</h1>
<form method="post" action="/hellomybatis/uploadServlet" enctype="multipart/form-data">
    選擇一個檔案:
    <input type="file" name="uploadFile" />
    <br/><br/>
    <input type="submit" value="上傳" />
</form>
</body>
</html>

message.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>檔案上傳結果</title>
</head>
<body>
<center>
    <h2>${message}</h2>
</center>
</body>
</html>

@WebServlet("/uploadServlet")
public class UploadServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    // 上傳檔案儲存目錄
    private static final String UPLOAD_DIRECTORY = "upload";

    // 上傳配置
    private static final int MEMORY_THRESHOLD   = 1024 * 1024 * 3;  // 3MB
    private static final int MAX_FILE_SIZE      = 1024 * 1024 * 40; // 40MB
    private static final int MAX_REQUEST_SIZE   = 1024 * 1024 * 50; // 50MB

    /**
     * 上傳資料及儲存檔案
     */
    @Override
    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response) throws ServletException, IOException {
        // 檢測是否為多媒體上傳
        if (!ServletFileUpload.isMultipartContent(request)) {
            // 如果不是則停止
            PrintWriter writer = response.getWriter();
            writer.println("Error: 表單必須包含 enctype=multipart/form-data");
            writer.flush();
            return;
        }

        // 配置上傳引數
        DiskFileItemFactory factory = new DiskFileItemFactory();
        // 設定記憶體臨界值 - 超過後將產生臨時檔案並儲存於臨時目錄中
        factory.setSizeThreshold(MEMORY_THRESHOLD);
        // 設定臨時儲存目錄
        factory.setRepository(new File(System.getProperty("java.io.tmpdir")));

        ServletFileUpload upload = new ServletFileUpload(factory);

        // 設定最大檔案上傳值
        //upload.setFileSizeMax(MAX_FILE_SIZE);

        // 設定最大請求值 (包含檔案和表單資料)
        upload.setSizeMax(MAX_REQUEST_SIZE);

        // 中文處理
        upload.setHeaderEncoding("UTF-8");

        // 構造臨時路徑來儲存上傳的檔案
        // 這個路徑相對當前應用的目錄
        String uploadPath = request.getServletContext().getRealPath("./") + File.separator + UPLOAD_DIRECTORY;


        // 如果目錄不存在則建立
        File uploadDir = new File(uploadPath);
        if (!uploadDir.exists()) {
            uploadDir.mkdir();
        }

        try {
            // 解析請求的內容提取檔案資料
            @SuppressWarnings("unchecked")
            List<FileItem> formItems = upload.parseRequest(request);

            if (formItems != null && formItems.size() > 0) {
                // 迭代表單資料
                for (FileItem item : formItems) {
                    // 處理不在表單中的欄位
                    if (!item.isFormField()) {
                        String fileName = new File(item.getName()).getName();
                        String filePath = uploadPath + File.separator + fileName;
                        File storeFile = new File(filePath);
                        // 在控制檯輸出檔案的上傳路徑
                        System.out.println(filePath);
                        // 儲存檔案到硬碟
                        item.write(storeFile);
                        request.setAttribute("message",
                                "檔案上傳成功!");
                    }
                }
            }
        } catch (Exception ex) {
            request.setAttribute("message",
                    "錯誤資訊: " + ex.getMessage());
        }
        // 跳轉到 message.jsp
        request.getServletContext().getRequestDispatcher("/message.jsp").forward(
                request, response);
    }
}

輸出:

Servlet 處理日期

使用 Servlet 的最重要的優勢之一是,可以使用核心 Java 中的大多數可用的方法。本章將講解 Java 提供的 java.util 包中的 Date 類,這個類封裝了當前的日期和時間。

   //初始化當前日期和時間的物件。
   Date today = new Date();
   //接受一個引數獲得時間物件,該引數等於 1970 年 1 月 1 日午夜以來經過的毫秒數。
   Date today1 = new Date(System.currentTimeMillis());
SimpleDateFormat ft = new SimpleDateFormat ("yyyy.MM.dd  hh:mm:ss E a ");

常用方法:

序號 方法 & 描述
1 boolean after(Date date) 如果呼叫的 Date 物件中包含的日期在 date 指定的日期之後,則返回 true,否則返回 false。
2 boolean before(Date date) 如果呼叫的 Date 物件中包含的日期在 date 指定的日期之前,則返回 true,否則返回 false。
3 Object clone( ) 重複呼叫 Date 物件。
4 int compareTo(Date date) 把呼叫物件的值與 date 的值進行比較。如果兩個值是相等的,則返回 0。如果呼叫物件在 date 之前,則返回一個負值。如果呼叫物件在 date 之後,則返回一個正值。
5 int compareTo(Object obj) 如果 obj 是 Date 類,則操作等同於 compareTo(Date)。否則,它會丟擲一個 ClassCastException。
6 boolean equals(Object date) 如果呼叫的 Date 物件中包含的時間和日期與 date 指定的相同,則返回 true,否則返回 false。
7 long getTime( ) 返回 1970 年 1 月 1 日以來經過的毫秒數。
8 int hashCode( ) 為呼叫物件返回雜湊程式碼。
9 void setTime(long time) 設定 time 指定的時間和日期,這表示從 1970 年 1 月 1 日午夜以來經過的時間(以毫秒為單位)。
10 String toString( ) 轉換呼叫的 Date 物件為一個字串,並返回結果。

使用 SimpleDateFormat 格式化日期

SimpleDateFormat 是一個以語言環境敏感的方式來格式化和解析日期的具體類。 SimpleDateFormat 允許您選擇任何使用者定義的日期時間格式化的模式。(非執行緒安全類)

簡單的日期格式的格式程式碼

使用事件模式字串來指定時間格式。在這種模式下,所有的 ASCII 字母被保留為模式字母,這些字母定義如下:

字元 描述 例項
G Era 指示器 AD
y 四位數表示的年 2001
M 一年中的月 July 或 07
d 一月中的第幾天 10
h 帶有 A.M./P.M. 的小時(1~12) 12
H 一天中的第幾小時(0~23) 22
m 一小時中的第幾分 30
s 一分中的第幾秒 55
S 毫秒 234
E 一週中的星期幾 Tuesday
D 一年中的第幾天 360
F 所在的周是這個月的第幾周 2 (second Wed. in July)
w 一年中的第幾周 40
W 一月中的第幾周 1
a A.M./P.M. 標記 PM
k 一天中的第幾小時(1~24) 24
K 帶有 A.M./P.M. 的小時(0~11) 10
z 時區 Eastern Standard Time
' Escape for text Delimiter
" 單引號 `

Servlet 網頁重定向

當文件移動到新的位置,我們需要向客戶端傳送這個新位置時,我們需要用到網頁重定向。當然,也可能是為了負載均衡,或者只是為了簡單的隨機,這些情況都有可能用到網頁重定向。

重定向請求到另一個網頁的最簡單的方式是使用 response 物件的 sendRedirect() 方法。也可以通過把 setStatus() 和 setHeader() 方法一起使用來達到同樣的效果 。

示例:

@WebServlet("/pageRedirect")
public class PageRedirectServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
            throws ServletException, IOException
    {
        // 設定響應內容型別
        response.setContentType("text/html;charset=UTF-8");

        // 要重定向的新位置
        //String site = new String("http://www.runoob.com");
         String site = "http://www.runoob.com";
        //方式一:
        //302
        //response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
        //response.setHeader("Location", site);
        //方式二:
        response.sendRedirect(site);
    }
}

Servlet 點選計數器

網頁點選計數器

很多時候,您可能有興趣知道網站的某個特定頁面上的總點選量。使用 Servlet 來計算這些點選量是非常簡單的,因為一個 Servlet 的生命週期是由它執行所在的容器控制的。

以下是實現一個簡單的基於 Servlet 生命週期的網頁點選計數器需要採取的步驟:

  • 在 init() 方法中初始化一個全域性變數。
  • 每次呼叫 doGet() 或 doPost() 方法時,都增加全域性變數。
  • 如果需要,您可以使用一個資料庫表來儲存全域性變數的值在 destroy() 中。在下次初始化 Servlet 時,該值可在 init() 方法內被讀取。這一步是可選的。
  • 如果您只想對一個 session 會話計數一次頁面點選,那麼請使用 isNew() 方法來檢查該 session 會話是否已點選過相同頁面。這一步是可選的。
  • 您可以通過顯示全域性計數器的值,來在網站上展示頁面的總點選量。這一步是可選的。

在這裡,我們假設 Web 容器將無法重新啟動。如果是重新啟動或 Servlet 被銷燬,計數器將被重置。

示例:

@WebServlet("/pageHit")
public class PageHitServlet extends HttpServlet {

    private int hitCount;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        // 增加 hitCount
        hitCount++;
        PrintWriter out = resp.getWriter();
        String title = "總點選量";
        String docType = "<!DOCTYPE html> \n";
        out.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + title + "</h1>\n" +
                "<h2 align=\"center\">" + hitCount + "</h2>\n" +
                "</body></html>");
    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }

    @Override
    public void init() throws ServletException {
        //初始化
        hitCount = 0;
    }

    @Override
    public void destroy() {
        //可以把 hitCount 的值寫入到資料庫
    }
}

請求:http://localhost:8080/hellomybatis/pageHit

輸出:

網站點選計數器

很多時候,您可能有興趣知道整個網站的總點選量。在 Servlet 中,這也是非常簡單的,我們可以使用過濾器做到這一點。

以下是實現一個簡單的基於過濾器生命週期的網站點選計數器需要採取的步驟:

  • 在過濾器的 init() 方法中初始化一個全域性變數。
  • 每次呼叫 doFilter 方法時,都增加全域性變數。
  • 如果需要,您可以在過濾器的 destroy() 中使用一個資料庫表來儲存全域性變數的值。在下次初始化過濾器時,該值可在 init() 方法內被讀取, 這一步是可選的。

web.xml

<filter>
  <filter-name>SiteHitCountFilter</filter-name>
  <filter-class>com.filter.SiteHitCountFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>SiteHitCountFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

public class SiteHitCountFilter implements Filter {

    private int hitCount;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //初始化
        hitCount = 0;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 把計數器的值增加 1
        hitCount++;

        // 輸出計數器
        System.out.println("網站訪問統計:"+ hitCount );

        // 把請求傳回到過濾器鏈
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
        //可以把 hitCount 的值寫入到資料庫
    }
}

輸出:

網站訪問統計:8
網站訪問統計:9
網站訪問統計:10
網站訪問統計:11

Servlet 傳送電子郵件

//待實踐

Servlet 國際化

三個重要術語:

  • 國際化(i18n):這意味著一個網站提供了不同版本的翻譯成訪問者的語言或國籍的內容。
  • 本地化(l10n):這意味著向網站新增資源,以使其適應特定的地理或文化區域,例如網站翻譯成印地文(Hindi)。
  • 區域設定(locale):這是一個特殊的文化或地理區域。它通常指語言符號後跟一個下劃線和一個國家符號。例如 "en_US" 表示針對 US 的英語區域設定。

Servlet 可以根據請求者的區域設定拾取相應版本的網站,並根據當地的語言、文化和需求提供相應的網站版本。以下是 request 物件中返回 Locale 物件的方法。

java.util.Locale request.getLocale()

檢測區域設定

下面列出了重要的區域設定方法,您可以使用它們來檢測請求者的地理位置、語言和區域設定。下面所有的方法都顯示了請求者瀏覽器中設定的國家名稱和語言名稱。

序號 方法 & 描述
1 String getCountry() 該方法以 2 個大寫字母形式的 ISO 3166 格式返回該區域設定的國家/地區程式碼。
2 String getDisplayCountry() 該方法返回適合向使用者顯示的區域設定的國家的名稱。
3 String getLanguage() 該方法以小寫字母形式的 ISO 639 格式返回該區域設定的語言程式碼。
4 String getDisplayLanguage() 該方法返回適合向使用者顯示的區域設定的語言的名稱。
5 String getISO3Country() 該方法返回該區域設定的國家的三個字母縮寫。
6 String getISO3Language() 該方法返回該區域設定的語言的三個字母的縮寫。

示例顯示某個請求的語言和相關的國家 :

@WebServlet("/locale")
public class GetLocaleServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
            throws ServletException, IOException
    {
        // 獲取客戶端的區域設定
        Locale locale = request.getLocale();
        String language = locale.getLanguage();
        String country = locale.getCountry();

        // 設定響應內容型別
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        String title = "檢測區域設定";
        String docType = "<!DOCTYPE html> \n";
        out.println(docType +
                "<html>\n" +
                "<head><title>" + title + "</title></head>\n" +
                "<body bgcolor=\"#f0f0f0\">\n" +
                "<h1 align=\"center\">" + language + "</h1>\n" +
                "<h2 align=\"center\">" + country + "</h2>\n" +
                "</body></html>");
    }
}

語言設定

Servlet 可以輸出以西歐語言(如英語、西班牙語、德語、法語、義大利語、荷蘭語等)編寫的頁面。在這裡,為了能正確顯示所有的字元,設定 Content-Language 頭是非常重要的。

第二點是使用 HTML 實體顯示所有的特殊字元,例如,"ñ" 表示 "ñ","¡" 表示 "¡"

 // 設定響應內容型別
    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();
    // 設定西班牙語言程式碼
    response.setHeader("Content-Language", "es");

特定於區域設定的日期

您可以使用 java.text.DateFormat 類及其靜態方法 getDateTimeInstance() 來格式化特定於區域設定的日期和時間。

 // 設定響應內容型別
    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();
    // 獲取客戶端的區域設定
    Locale locale = request.getLocale( );
    String date = DateFormat.getDateTimeInstance(
                                  DateFormat.FULL, 
                                  DateFormat.SHORT, 
                                  locale).format(new Date( ));

特定於區域設定的貨幣

您可以使用 java.text.NumberFormat 類及其靜態方法 getCurrencyInstance() 來格式化數字(比如 long 型別或 double 型別)為特定於區域設定的貨幣。

  // 設定響應內容型別
    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();
    // 獲取客戶端的區域設定
    Locale locale = request.getLocale( );
    NumberFormat nft = NumberFormat.getCurrencyInstance(locale);
    String formattedCurr = nft.format(1000000);

特定於區域設定的百分比

您可以使用 java.text.NumberFormat 類及其靜態方法 getPercentInstance() 來格式化特定於區域設定的百分比。

 // 設定響應內容型別
    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();
    // 獲取客戶端的區域設定
    Locale locale = request.getLocale( );
    NumberFormat nft = NumberFormat.getPercentInstance(locale);
    String formattedPerc = nft.format(0.51);

疑問

Q:Servlet 在 Web 伺服器的地址空間內執行。這樣它就沒有必要再建立一個單獨的程式來處理每個客戶端請求。這句話怎麼理解?

Q:為什麼工作中遇到的JSP頁面一般不會寫java程式碼,而是html與js或者說jquery整合?

A:讓Servlet只負責業務邏輯部分,而不會生成HTML程式碼;同時JSP中也不會充斥著大量的業務程式碼,這樣能大提高了程式碼的可讀性和可維護性。這是因為我們採用MVC模式,這樣一來避免了Servlet通過字串拼接的方式生成動態HTML內容,這樣就容易導致程式碼維護困難、可讀性差的缺點,也避免了JSP在HTML中混入大量、複雜的業務邏輯。

Q:什麼是函數語言程式設計?

Q:servlet怎麼請求jsp頁面?

一種:在webapp下的jsp檔案可以通過如這個連結直接訪問,為什麼把jsp檔案放在建立的資料夾下就不行了呢?http://localhost:8080/hellomybatis/upload.jsp

擴充套件閱讀

單點登入技術

單點登入的目的是為了能在分散式系統或者叢集中對使用者的請求校驗登入狀態。

一般我們用到的是共享session或者唯一token來實現。共享session一般是給網頁或者說http請求用,而token一般是給安卓、IOS系統等前端用。

在這裡我們說下共享session。一般我們會用單點登入系統或者是在tomcat上配置redis,當使用者請求頁面的時候,會根據使用者在cookie裡預設的sessionId到tomcat上配置redis配置的redis上校驗是否存在,存在則不同的系統都共有這個session會話。不存在則跳到登入頁面登入。理論上單點登入系統也是這樣,一般是通過redis來快取session會話,實現單點登入。

servlet和jsp的區別

ervlet和sp的區別

1、Servlet在Java程式碼中可以通過HttpServletResponse物件動態輸出HTML內容。

2、JSP是在靜態HTML內容中嵌入Java程式碼,然後Java程式碼在被動態執行後生成HTML內容。

servlet和jsp的各自的特點

1、Servlet雖然能夠很好地組織業務邏輯程式碼,但是在Java原始檔中,因為是通過字串拼接的方式生成動態HTML內容,這樣就容易導致程式碼維護困難、可讀性差。

2、JSP雖然規避了Servlet在生成HTML內容方面的劣勢,但是在HTML中混入大量、複雜的業務邏輯。

通過MVC雙劍合璧

JSP和Servlet都有自身的適用環境,那麼有沒有什麼辦法能夠讓它們發揮各自的優勢呢?答案是肯有的,MVC模式就能夠完美解決這一問題。

MVC模式,是Model-View-Controller的簡稱,是軟體工程中的一種軟體架構模式,分為三個基本部分,分別是:模型(Model)、檢視(View)和控制器(Controller):

Controller——負責轉發請求,對請求進行處理

View——負責介面顯示

Model——業務功能編寫(例如演算法實現)、資料庫設計以及資料存取操作實現

在JSP/Servlet開發的軟體系統中,這三個部分的描述如下所示:

1、Web瀏覽器傳送HTTP請求到服務端,然後被Controller(Servlet)獲取並進行處理(例如引數解析、請求轉發)

2、Controller(Servlet)呼叫核心業務邏輯——Model部分,獲得結果

3、Controller(Servlet)將邏輯處理結果交給View(JSP),動態輸出HTML內容

4、動態生成的HTML內容返回到瀏覽器顯示

MVC模式在Web開發中有很大的優勢,它完美規避了JSP與Servlet各自的缺點,讓Servlet只負責業務邏輯部分,而不會生成HTML程式碼;同時JSP中也不會充斥著大量的業務程式碼,這樣能大提高了程式碼的可讀性和可維護性。

相關文章