這篇我們來針對 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 ,就會直接被擋下來,不會進到我們的 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