使用 Servlet Filter

使用 Java Servlet Filter 進行簡單過濾

Servlet Filter Banner

就先來建立簡單的 Servlet 服務吧!📗環境建立請參考前面的文章📗

先來看看我的專案目錄:

├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── judysocute
│   │   │           ├── Add.java
│   │   │           ├── Divided.java
│   │   │           ├── Minus.java
│   │   │           └── Multiply.java
│   │   ├── resources
│   │   └── webapp
│   │       ├── WEB-INF
│   │       └── index.html

簡單 HTML表單

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>兩數加減乘除頁面</title>
</head>
<body>
    <form action="operation/add">
        <h3>相加</h3>
        <input type="text" name="num1">
        <input type="text" name="num2">
        <input type="submit" value="相加">
    </form>

    <form action="operation/minus">
        <h3>相減</h3>
        <input type="text" name="num1">
        <input type="text" name="num2">
        <input type="submit" value="相減">
    </form>

    <form action="operation/multiply">
        <h3>相乘</h3>
        <input type="text" name="num1">
        <input type="text" name="num2">
        <input type="submit" value="相乘">
    </form>

    <form action="operation/divided">
        <h3>相除</h3>
        <input type="text" name="num1">
        <input type="text" name="num2">
        <input type="submit" value="相除">
    </form>
</body>
</html>

加、減、乘、除 Servlet

/**
 * Add.java
 * 加
 */
@WebServlet(urlPatterns = "/operation/add")
public class Add extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        int num1 = Integer.parseInt(req.getParameter("num1"));
        int num2 = Integer.parseInt(req.getParameter("num2"));

        PrintWriter printer = resp.getWriter();
        printer.print(num1 + num2);
    }
}
/**
 * Minus.java
 * 減
 */
@WebServlet(urlPatterns = "/operation/minus")
public class Minus extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        int num1 = Integer.parseInt(req.getParameter("num1"));
        int num2 = Integer.parseInt(req.getParameter("num2"));

        PrintWriter printer = resp.getWriter();
        printer.print(num1 - num2);
    }
}
/**
 * Multiply.java
 * 乘
 */
@WebServlet(urlPatterns = "/operation/multiply")
public class Multiply extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        int num1 = Integer.parseInt(req.getParameter("num1"));
        int num2 = Integer.parseInt(req.getParameter("num2"));

        PrintWriter printer = resp.getWriter();
        printer.print(num1 * num2);
    }
}
/**
 * Divided.java
 * 除
 */
@WebServlet(urlPatterns = "/operation/divided")
public class Divided extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        int num1 = Integer.parseInt(req.getParameter("num1"));
        int num2 = Integer.parseInt(req.getParameter("num2"));

        PrintWriter printer = resp.getWriter();
        printer.print(num1 / num2);
    }
}

其實是很基礎的東西 差別也都只有差在最後的運算而已 服務做到這邊基本可以算是完成了 但是我們可以發現不少問題:

  • 沒輸入任何數字 會報錯
  • 輸入了非數字的字串 如 "abc" 會報錯
  • 除法中輸入了 "0" 會報錯

我們最簡單的找到了這幾個問題,當然是可以在個別 Servlet 中解決,每個都判斷一次就好了,不喜歡程式碼重複也可以建立一個類別共同管理方法 這些解法當然都沒有錯,但是有點太麻煩了

以我們上面的發現的問題可以歸類出,不管是 加、減、乘、除, 只要輸入了 字串 或是 沒輸入 都會報錯 只有除法的時候,除數是不能為0 其實已經有一點點複雜了

那如果有一個方法,可以保證我們 Servlet 收到的值都不會有錯,一定是可以直接進行運算的呢? 那麼我們的 Servlet 不就可以只專注在運算邏輯,而不用花心思在處理例外,豈不美哉? 當我們出現了這種思維的時候 Filter 就可以幫助我們!

我們的想法是,我們把剛才列出來的條件都做成一個 Filter,像這樣

Filter Image

當使用者傳入的值不通過這些 Filter ,就會直接被擋下來,不會進到我們的 Servlet 去,這樣就達成了我們的目標: 「Servlet 可以專注在運算邏輯,而不用花心思在處理例外上」

那... 怎麽實作出來呢?


再看一下我們的目錄結構

├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── judysocute
│   │   │           ├── Add.java
│   │   │           ├── Divided.java
│   │   │           ├── Minus.java
│   │   │           ├── Multiply.java
│   │   │           └── filter
│   │   │               ├── NullFilter.java
│   │   │               ├── StringFilter.java
│   │   │               └── ZeroFilter.java
│   │   ├── resources
│   │   └── webapp
│   │       ├── WEB-INF
│   │       │   └── web.xml
│   │       └── index.html

我們多了一個 filter package 來存放管理我們所有的 Filter

/**
 * NullFilter.java
 * 判斷傳入是否為 null 或 空字串
 */
public class NullFilter extends HttpFilter {
    @Override
    protected void doFilter(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) throws IOException, ServletException {
        System.out.println("NullFilter");
        String num1 = req.getParameter("num1");
        String num2 = req.getParameter("num2");
        if (num1 != null && num2 != null && !num1.isEmpty() && !num2.isEmpty()) {
            chain.doFilter(req, resp);
        } else {
            resp.setContentType("text/plain; charset=UTF-8");
            PrintWriter printer = resp.getWriter();
            printer.print("欄位不能留空,請確實填入要運算的數字");
        }

    }
}
/**
 * StringFilter.java
 * 判斷輸入的值是否可以轉換為數字
 */
public class StringFilter extends HttpFilter {
    @Override
    protected void doFilter(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) throws IOException, ServletException {
        System.out.println("StringFilter");
        try {
            Integer.parseInt(req.getParameter("num1"));
            Integer.parseInt(req.getParameter("num2"));
            chain.doFilter(req, resp);
        } catch (NumberFormatException e) {
            resp.setContentType("text/plain; charset=UTF-8");
            PrintWriter printer = resp.getWriter();
            printer.print("欄位請填入數字");
        }
    }
}
/**
 * ZeroFilter.java
 * 專給除法使用的 Filter 判斷除數是否為 0
 */
public class ZeroFilter extends HttpFilter {
    @Override
    protected void doFilter(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) throws IOException, ServletException {
        System.out.println("ZeroFilter");
        int num2 = Integer.parseInt(req.getParameter("num2"));
        if (num2 != 0) {
            chain.doFilter(req, resp);
        } else {
            resp.setContentType("text/plain; charset=UTF-8");
            PrintWriter printer = resp.getWriter();
            printer.print("除法的除數不可為 0 ");
        }
    }
}

這次的 Filter 順序就很重要了! 我們心中的順序是 NullFilter -> StringFilter -> ZeroFilter 如果我們在還沒有判斷過是否為 null 或是否可以轉換為數字之前,就進入到了除法的 ZeroFilter,會直接錯掉 因為有可能傳入 "abc",原先我們預期會被 StringFilter 過濾掉,可是我們不知道 Filter 背後的實作順序 我們用預設的,就很有可能是 NullFilter -> ZeroFilter -> StringFilter 很顯然的這樣會錯掉,所以這次我不用 WebFilter Annotation 來定義 urlPattern,我改用 web.xml 來定義

<!--  web.xml  -->
<web-app version="4.0"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd">
    <filter>
        <filter-name>NullFilter</filter-name>
        <filter-class>com.judysocute.filter.NullFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>NullFilter</filter-name>
        <url-pattern>/operation/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>StringFilter</filter-name>
        <filter-class>com.judysocute.filter.StringFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>StringFilter</filter-name>
        <url-pattern>/operation/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>ZeroFilter</filter-name>
        <filter-class>com.judysocute.filter.ZeroFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>ZeroFilter</filter-name>
        <url-pattern>/operation/divided</url-pattern>
    </filter-mapping>

</web-app>

順序會由 xml 定義的上至下,這樣就能保證是我們想要的順序了!