使用 Servlet Filter

這篇我們來針對 Servlet Filter 做一個簡單的兩數加減乘除的練習!

就先來建立簡單的 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 示意圖

當使用者傳入的值不通過這些 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 定義的上至下,這樣就能保證是我們想要的順序了!

這次專案的程式碼一樣上 github 了!
歡迎拉下來看看!
https://github.com/judysocute/filter-tutorial

Leave a Reply