Hướng dẫn sử dụng Java Servlet Filter
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- Giới thiệu

Tài liệu được viết dựa trên:
  • Eclipse 4.5 MARS (Ok for Eclipse 4.4 LUNA)

  • Tomcat 8.x

Bạn cần có kiến thức về Servlet trước khi đọc tài liệu về Servlet-Filter, nếu là người mới bắt đầu, bạn có thể xem Java Servlet tại:

2- Tại sao cần Server-Filter?

Tình huống 1:

Thông thường khi người dùng yêu cầu một trang web, một request sẽ được gửi tới server, nó sẽ phải đi qua các Filter trước khi tới trang yêu cầu, giống hình minh họa dưới đây.

Tình huống 2:

Tuy nhiên có những tình huống request của người dùng không vượt qua được hết các tầng Filter.

Tình huống 3:

Tình huống người dùng gửi yêu cầu một trang (page1), yêu cầu này sẽ phải vượt qua các Filter, tại một filter nào đó yêu cầu bị chuyển hướng sang một trang khác (page2).

Tình huống ví dụ:
  1. Người dùng gửi yêu cầu xem trang thông tin cá nhân.
  2. Request sẽ được gửi tới Server.
  3. Nó vượt qua Filter ghi lại thông tin log.
  4. Nó tới Filter kiểm tra người dùng đã đăng nhập chưa, filter này kiểm tra thấy người dùng chưa đăng nhập, nó chuyển hướng yêu cầu của người dùng sang trang login.

3- Servlet-Filter làm được những gì?

Đôi khi bạn chỉ quan niệm rằng Filter sử dụng để chuyển yêu cầu người dùng tới một trang khác, hoặc ngăn chặn truy cập vào một trang web nào đó nếu người dùng không có quyền. Hoặc sử dụng để ghi lại các thông tin Log.
Trong thực tế Filter có thể sử dụng để mã hóa trang web. Ví dụ như sét đặt mã hóa UTF-8 cho trang. Mở và đóng kết nối tới Database và chuẩn bị giao dịch JDBC (JDBC Transaction).

4- Tạo Project bắt đầu với Servlet-Filter

Trước hết chúng ta tạo một WebApp project để làm việc với Servlet-Filter.
  • File/New/Other...
Nhập vào:
  • Project Name: ServletFilterTutorial
Project đã được tạo ra:
Tạo file index.html:
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Home Page</title>
</head>
<body>

  <h2>Servlet-Filter Tutorial</h2>

</body>
</html>

5- Cấu hình môi trường chạy

Nhấn phải vào Project chọn Properties:
Nhấn phải vào Project chọn:
  • Run As/Run on Server
OK!, mọi thứ đã sẵn sàng để bắt đầu học Servlet-Filter.

6- Ví dụ Servlet-Filter đầu tiên

Servlet-Filter là một class thi hành interface javax.servlet.Filter. Class LogFilter dưới đây ghi lại thời gian và đường dẫn mà yêu cầu gửi tới WebApp.
LogFilter.java
package org.o7planning.tutorial.servletfilter;

import java.io.IOException;
import java.util.Date;

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 LogFilter implements Filter {

  public LogFilter() {
  }

  @Override
  public void init(FilterConfig fConfig) throws ServletException {
      System.out.println("LogFilter init!");
  }

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

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
          throws IOException, ServletException {

      HttpServletRequest req = (HttpServletRequest) request;

      String servletPath = req.getServletPath();

      System.out.println("#INFO " + new Date()
              + " - ServletPath :" + servletPath + ", URL =" + req.getRequestURL());

      // Cho phép request được đi tiếp. (Vượt qua Filter này).
      chain.doFilter(request, response);
  }

}
Bạn cần đăng ký các đường dẫn phải đi qua Filter này trong web.xml:
Thêm vào đoạn cấu hình trong web.xml:
<!--
 Khai báo một filter có tên logFilter
-->
<filter>
  <filter-name>logFilter</filter-name>
  <filter-class>org.o7planning.tutorial.servletfilter.LogFilter</filter-class>
</filter>

<!--
 Khai báo các đường dẫn (của trang) sẽ chịu tác dụng của logFilter
 /* có nghĩa là mọi đường dẫn.
-->
<filter-mapping>
  <filter-name>logFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns="http://java.sun.com/xml/ns/javaee"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                               http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
   id="WebApp_ID" version="3.0">

<display-name>ServletFilterTutorial</display-name>

<filter>
 <filter-name>logFilter</filter-name>
 <filter-class>org.o7planning.tutorial.servletfilter.LogFilter</filter-class>
</filter>

<filter-mapping>
 <filter-name>logFilter</filter-name>
 <url-pattern>/*</url-pattern>
</filter-mapping>



<welcome-file-list>
 <welcome-file>index.html</welcome-file>
 <welcome-file>index.htm</welcome-file>
 <welcome-file>index.jsp</welcome-file>
 <welcome-file>default.html</welcome-file>
 <welcome-file>default.htm</welcome-file>
 <welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>
Chạy lại ứng dụng của bạn:
Bạn có thể chạy các đường dẫn sau trên trình duyệt, ở đây có cả những đường dẫn tới các nguồn không hề tồn tại trên WebApp của bạn, tuy nhiên nó vẫn chịu tác dụng của LogFilter.
Các thông tin Log được ghi ra trên màn hình Console:
Đoạn code cho phép request vượt qua Filter để tiếp tục tiến tới trang yêu cầu.
// Cho phép request vượt qua Filter này để tiếp tục.
// Nó có thể tới một Filter khác hoặc tới trang người dùng yêu cầu.

chain.doFilter(request, response);

7- Mô hình làm việc của Filter

Khi người dùng gửi một yêu cầu, mục tiêu (target) có thể là một nguồn dữ liệu (resource) hoặc một servlet. Request cần phải đi qua các Filter và cuối cùng là mục tiêu. Các filter và mục tiêu được xích lại (chained) với nhau giống hình minh họa dưới đây:
Sử dụng chain.doFilter(request,response) để di chuyển request tới mắt xích tiếp theo. Nếu trong filter chain.doFilter(request, response) không được gọi, yêu cầu của người dùng sẽ không đến được mục tiêu, nó bị dừng lại tại filter đó.

8- Tham số Khởi tạo cho Servlet-Filter

Cũng giống với Servlet, bạn có thể khởi tạo các tham số cho Filter. Ví dụ dưới đây một Filter làm nhiệm  vụ ghi ra log vào một file, bạn có thể cấu hình trong web.xml tên file để ghi.
Log2Filter.java
package org.o7planning.tutorial.servletfilter;

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;

public class Log2Filter implements Filter {

  private String logFile;

  public Log2Filter() {
  }

  @Override
  public void init(FilterConfig fConfig) throws ServletException {
      this.logFile = fConfig.getInitParameter("logFile");

      System.out.println("Log File " + logFile);
  }

  @Override
  public void destroy() {
      System.out.println("Log2Filter destroy!");
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
          throws IOException, ServletException {

      if (this.logFile != null) {
          // Ghi thông tin Log vào File.
          this.logToFile(this.logFile);
      }

      // Cho phép request được đi tiếp. (Vượt qua Filter này).
      chain.doFilter(request, response);
  }

  private void logToFile(String fileName) {
      // Ghi log vào file.
      System.out.println("Write log to file " + fileName);
  }

}
Thêm đoạn cấu hình vào web.xml:
<filter>
   <filter-name>log2Filter</filter-name>
   <filter-class>org.o7planning.tutorial.servletfilter.Log2Filter</filter-class>
   <init-param>
       <param-name>logFile</param-name>
       <param-value>AppLog.log</param-value>
   </init-param>
</filter>

<filter-mapping>
   <filter-name>log2Filter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>ServletFilterTutorial</display-name>

<filter>
  <filter-name>logFilter</filter-name>
  <filter-class>org.o7planning.tutorial.servletfilter.LogFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>logFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>


<filter>
  <filter-name>log2Filter</filter-name>
  <filter-class>org.o7planning.tutorial.servletfilter.Log2Filter</filter-class>
  <init-param>
      <param-name>logFile</param-name>
      <param-value>AppLog.log</param-value>
  </init-param>
</filter>

<filter-mapping>
  <filter-name>log2Filter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
 


<welcome-file-list>
  <welcome-file>index.html</welcome-file>
  <welcome-file>index.htm</welcome-file>
  <welcome-file>index.jsp</welcome-file>
  <welcome-file>default.html</welcome-file>
  <welcome-file>default.htm</welcome-file>
  <welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>

9- Servlet-Filter url-pattern

Có 3 mẫu để bạn cấu hình url-pattern cho Filter:
URL Pattern Ví dụ
/* http://example.com/contextPath
http://example.com/contextPath/status/abc
/status/abc/* http://example.com/contextPath/status/abc
http://example.com/contextPath/status/abc/mnp
http://example.com/contextPath/status/abc/mnp?date=today
http://example.com/contextPath/test/abc/mnp
*.map http://example.com/contextPath/status/abc.map
http://example.com/contextPath/status.map?date=today
http://example.com/contextPath/status/abc.MAP

10- Servlet-Filter sử dụng Annotation

Các ví dụ ở trên cấu hình Filter trong web.xml, tuy nhiên với Servlet phiên bản 3 trở lên bạn có thể sử dụng Annotation để cấu hình cho Filter.

Ví dụ này minh họa khi người dùng gửi yêu cầu xem một file ảnh ( jpg, png hoặc gif), Filter sẽ kiểm tra xem file ảnh có tồn tại không, trong trường hợp không tồn tại filter sẽ chuyển hướng yêu cầu tới một file ảnh mặc định.
Trước hết bạn copy 2 file ảnh flower.png & image-not-found.png vào thư mục images trên WebApp của bạn.
 
ImageFilter.java
package org.o7planning.tutorial.servletfilter;

import java.io.File;
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.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebFilter(urlPatterns = { "*.png", "*.jpg", "*.gif" },
      initParams = {        
          @WebInitParam(name = "notFoundImage", value = "/images/image-not-found.png")        
      }
)
public class ImageFilter implements Filter {

  private String notFoundImage;

  public ImageFilter() {
  }

  @Override
  public void init(FilterConfig fConfig) throws ServletException {

      // ==> /images/image-not-found.png
      notFoundImage = fConfig.getInitParameter("notFoundImage");
  }

  @Override
  public void destroy() {
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
          throws IOException, ServletException {

      HttpServletRequest req = (HttpServletRequest) request;

      // ==> /images/path/my-image.png
      // ==> /path1/path2/image.pngs
      String servletPath = req.getServletPath();

      // Đường dẫn thực sự trên Server đến thư mục gốc (WebContent).
      String realRootPath = request.getServletContext().getRealPath("");

      // Đường dẫn thực tới file ảnh.
      String imageRealPath = realRootPath + servletPath;

      System.out.println("imageRealPath = " + imageRealPath);

      File file = new File(imageRealPath);

      // Kiểm tra xem ảnh có tồn tại không
      if (file.exists()) {

          // Cho phép request được đi tiếp. (Vượt qua Filter này).
          // (Để đi tiếp tới file ảnh yêu cầu).
          chain.doFilter(request, response);

      } else if (!servletPath.equals(this.notFoundImage)) {

          // Chuyển hướng tới 'image not found' image.
          HttpServletResponse resp = (HttpServletResponse) response;
         
          // ==> /ServletFilterTutorial + /images/image-not-found.png
          resp.sendRedirect(req.getContextPath()+ this.notFoundImage);

      }

  }

}
Chạy lại ứng dụng của bạn và chạy thử các URL sau:
Với các đường dẫn yêu cầu file ảnh, mà file ảnh không tồn tại, nó sẽ bị chuyển hướng sang ảnh mặc định.
Trong ví dụ trên bạn cũng có thể sử dụng Forward thay vì Redirect request tới file ảnh mặc định trong trường hợp file ảnh yêu cầu không tồn tại.
// Redirect:

// ==> /ServletFilterTutorial + /images/image-not-found.png
resp.sendRedirect(req.getContextPath()+ this.notFoundImage);

// Forward:

req.getServletContext().getRequestDispatcher(this.notFoundImage).forward(request, response);

11- Thiết lập kết nối JDBC trong Filter

 Bạn có thể tạo đối tượng Connection kết nối JDBC trong Servlet để sử lý công việc với Database. Tuy nhiên bạn cũng có thể tạo đối tượng Connection kết nối JDBC trong Filter, và nó sẽ có tác dụng với nhiều Servlet. Và bạn có thể sử dụng Connection này trong suốt đường đi của request. Để dễ hiểu bạn có thể xem hình minh họa dưới đây:
ConnectionUtils là class tạo ra đối tượng Connection kết nối với database, trong tài liệu này tôi không giới thiệu chi tiết làm cách nào bạn có được đối tượng Connection.
Bạn có thể tìm hiểu tài liệu JDBC tại:
ConnectionUtils.java
package org.o7planning.tutorial.servletfilter.conn;

import java.sql.Connection;

public class ConnectionUtils {

   public static Connection getConnection() {

       // Tạo một connection kết nối vào Database.
       Connection conn = null;

       // .....

       return conn;
   }

   public static void closeQuietly(Connection conn) {
       try {
           conn.close();
       } catch (Exception e) {
       }
   }

   public static void rollbackQuietly(Connection conn) {
       try {
           conn.rollback();
       } catch (Exception e) {
       }
   }
}
MyUtils.java
package org.o7planning.tutorial.servletfilter.conn;

import java.sql.Connection;

import javax.servlet.ServletRequest;

public class MyUtils {

   public static final String ATT_NAME = "MY_CONNECTION_ATTRIBUTE";

   // Lưu trữ Connection vào một thuộc tính của request.
   // Thông tin lưu trữ chỉ tồn tại trong thời gian yêu cầu (request)
   // cho tới khi dữ liệu được trả về trình duyệt người dùng.
   public static void storeConnection(ServletRequest request, Connection conn) {
       request.setAttribute(ATT_NAME, conn);
   }

   // Lấy đối tượng Connection đã được lưu trữ trong 1 thuộc tính của request.
   public static Connection getStoredConnection(ServletRequest request) {
       Connection conn = (Connection) request.getAttribute(ATT_NAME);
       return conn;
   }
}
Tôi khai báo url-pattern cho JDBCFilter/*, filter này sẽ có tác dụng với tất cả các yêu cầu của người dùng.
JDBCFilter.java
package org.o7planning.tutorial.servletfilter.conn;

import java.io.IOException;
import java.sql.Connection;

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.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

@WebFilter(urlPatterns = { "/*" })
public class JDBCFilter implements Filter {

   public JDBCFilter() {
   }

   @Override
   public void init(FilterConfig fConfig) throws ServletException {

   }

   @Override
   public void destroy() {

   }

   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
           throws IOException, ServletException {

       HttpServletRequest req = (HttpServletRequest) request;

       //
       String servletPath = req.getServletPath();

       //
       // Chỉ mở kết nối đối với các request có đường dẫn đặc biệt cần
       // connection. (Chẳng hạn đường dẫn tới các servlet, jsp, ..)
       //
       // Tránh tình trạng mở connection với các yêu cầu thông thường
       // (chẳng hạn image, css, javascript,... )
       //
       if (servletPath.contains("/specialPath1") || servletPath.contains("/specialPath2")) {
           Connection conn = null;
           try {
               // Tạo đối tượng Connection kết nối database.
               conn = ConnectionUtils.getConnection();
               // Sét tự động commit false, để chủ động điều khiển.
               conn.setAutoCommit(false);

               // Lưu trữ vào attribute của request.
               MyUtils.storeConnection(request, conn);

               // Cho phép request đi tiếp.
               chain.doFilter(request, response);

               // Gọi commit() để commit giao dịch với DB.
               conn.commit();
           } catch (Exception e) {
               ConnectionUtils.rollbackQuietly(conn);
               throw new ServletException();
           } finally {
               ConnectionUtils.closeQuietly(conn);
           }
       }
       // Với các request thông thường, cho tiếp tục.
       else {
           // Cho phép request đi tiếp.
           chain.doFilter(request, response);
       }

   }

}
Tại filter tiếp theo hoặc tại servlet, hoặc tại trang JSP (trên cùng một yêu cầu), bạn có thể lấy đối tượng Connection đã lưu trữ trong attribute của request:
Connection conn = (Connection) request.getAttribute(ATT_NAME);

// Hoặc

Connection conn = MyUtils.getStoredConnection();

12- Hướng dẫn lập trình JSP

Tiếp theo bạn có thể tìm hiểu về JSP: