openplanning

Hướng dẫn và ví dụ Java Servlet Filter

  1. Giới thiệu
  2. Tại sao cần Server-Filter?
  3. Servlet-Filter làm được những gì?
  4. Tạo Project bắt đầu với Servlet-Filter
  5. Cấu hình môi trường chạy
  6. Ví dụ Servlet-Filter đầu tiên
  7. Mô hình làm việc của Filter
  8. Tham số Khởi tạo cho Servlet-Filter
  9. Servlet-Filter url-pattern
  10. Servlet-Filter sử dụng Annotation
  11. Thiết lập kết nối JDBC trong Filter
  12. Hướng dẫn lập trình JSP

1. Giới thiệu

Tài liệu được viết dựa trên:
  • Eclipse 4.6 NEON

  • 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 bộ lọ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 (encoding) 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 của 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 bộ lọc (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 sau cho phép request vượt qua bộ lọc (Filter) để tiếp tục tiến tới đích (trang mong muốn).
// Cho phép request đi tiếp lên phía trước.
// Nó có thể tới một Filter tiếp theo hoặc tới mục tiê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
/status/abc/*
http://example.com/contextPath/status/abc/mnp
/status/abc/*
http://example.com/contextPath/status/abc/mnp?date=today
/status/abc/*
http://example.com/contextPath/test/abc/mnp
*.map
http://example.com/contextPath/status/abc.map
*.map
http://example.com/contextPath/status.map?date=today
*.map
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 WebApp 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 tuyệt đối của thư mục gốc của WebApp (WebContent).
		String realRootPath = request.getServletContext().getRealPath("");

		// Đường dẫn tuyệt đối 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)) {

			// Redirect (Chuyển hướng) tới file ảnh 'image not found'.
			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 chuyển tiếp (Forward) thay vì chuyển hướng (Redirect) yêu cầu 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
response.sendRedirect(request.getContextPath()+ this.notFoundImage);

// Forward:

request.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) tới 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ữ đối tượng Connection vào một thuộc tính (attribute) 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ở Connection (kết nối) đối với các request có đường dẫn đặc biệt
		// (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 thuộc tính (attribute) của request.
				MyUtils.storeConnection(request, conn);

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

				// Gọi commit() để hoàn thành giao dịch (transaction) với DB.
				conn.commit();
			} catch (Exception e) {
				ConnectionUtils.rollbackQuietly(conn);
				throw new ServletException();
			} finally {
				ConnectionUtils.closeQuietly(conn);
			}
		}
		// Đối với các request thông thường.
		else {
			// Cho phép request đi tiếp (Vượt qua Filter này).
			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 thuộc tính (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: