Руководство Java Servlet Filter

1- Введение

Статья основана на:
  • Eclipse 4.6 NEON

  • Tomcat 8.x

Вы должны иметь знание о Servlet перед тем, как прочитать статью про  Servlet- Filter, если вы начинающий, вы можете посмотреть  Java Servlet по ссылке:

2- Почему нужен Server-Filter?

Ситуация 1:

Обычно, когда пользователь запрашивает веб-страницу, запрос будет отправлен на server, он должен проходить через фильтры (Filter) до того как дойти до запрошенной страницы, как в изображении ниже.

Ситуация 2:

Однако существуют ситуации запроса пользователя, которые не проходят все уровни  Filter.

Ситуация 3:

Ситуация когда пользователь отправляет запрос одной страницы (page1), этот запрос проходит через Filter, у определенного filter запрос будет перенаправлен на другую страницу (page2).
Пример ситуации:
  1. Пользователь посылает запрос, чтобы посмотреть страницу с личной информацией.
  2. Запрос будет отправлен на Server.
  3. Она проходит Filter, который записывает информацию log.
  4. Идет к Filter и проверяет вошел ли пользователь в систему, filter проверил, и увидел, что пользователь не вошел в систему, то перенаправляет запрос на страницу входа в систему пользователя.

3- Что может сделать Servlet-Filter?

Иногда у вас есть только одно понятие это Filter используется чтобы перенаправить запрос пользователя на другую страницу, или блокировать доступ к определенному сайту, если пользователь не имеет прав. Или используется для записи информации Log.
На самом деле Filter может быть использован для кодирования вебсайта (encoding). Например, установить кодировку UTF-8 для страницы. Открыть и закрыть соединение  к Database и подготовить транзакцию JDBC (JDBC Transaction).

4- Создать Project для начала с Servlet-Filter

Сначала мы создаем WebApp проект для работы с Servlet-Filter.
  • File/New/Other...
Ввести:
  • Project Name: ServletFilterTutorial
Проект создан:
Создать файл 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- Конфигурация среды запуска

Щелкните правой кнопкой мыши на проект и выберите Properties
Щелкните правой кнопкой мыши на проект и выберите:
  • Run As/Run on Server
ОК!, Все готово, чтобы начать изучать  Servlet- Filter.

6- Первый пример Servlet-Filter 

Kласс Servlet Filter, который выполняет interface  javax.servlet.Filter. Класс LogFilter ниже записывает время и путь запроса, отправленного к 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());

       
        // Go to the next element (filter or target) in chain.
        chain.doFilter(request, response);
    }

}
Configure filter in web.xml:
Добавить в фрагмент конфигурации  в web.xml:
<!--
  Declaring a filter named logFilter
-->
<filter>
  <filter-name>logFilter</filter-name>
  <filter-class>org.o7planning.tutorial.servletfilter.LogFilter</filter-class>
</filter>

<!--
  Declare the path (of the page) will have the effect of logFilter
 /* for all paths
-->
<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>
Перезапустите приложение:
Вы можете запустить следующие ссылки в браузере, здесь есть ссылки на источники, которые не существуют на вашем WebApp, но они все находятся под влиянием LogFilter.
Информация Log записывается на экране Console:
Следующий код позволяет запросу пройти фильтр (Filter) чтобы продолжить до цели (желаемой страницы).
// 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- Модель работы Filter

Когда пользователь посылает запрос, цель (target) может быть источником данных (resource) или servlet. Запрос должен пройти через Filter и, наконец дойти до цели. Filter и цели сцеплены (chained) вместе как в изображении ниже:
Используйте chain.doFilter(request,response), чтобы переместить запрос к следующей ссылке. Если в filter chain.doFilter (request, response) не вызывается, запрос пользователя не достигнет цели, он останавливается в этом фильтре.

8- Инициализированные параметры Servlet-Filter

Как и с  Servlet, вы можете инициализировать параметры для Filter. В примере ниже, Filter имеет обязанность записи log в файл, вы можете настроить конфигурацию в web.xml имя файла для записи.
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) {
            // Write log to file
            this.logToFile(this.logFile);
        }

      
        // Go to the next element (filter or target) in chain.
        chain.doFilter(request, response);
    }

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

}
Добавьте конфигурацию в 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

Существуют 3 способа как вы можете конфигурировать url-pattern для  Filter:
URL Pattern Example
/* 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 используя Annotation

В примерах выше Filter конфигурируется в  web.xlm, однако с WebApp версии 3 и далее, вы можете использовать Annotation для конфигурации Filter.

Этот пример иллюстрирует, когда пользователь запрашивает просмотр файла изображения (JPG, PNG или GIF), filter будет проверять, существует изображение или нет, при отсутствии изображения, Filter перенаправит запрос на файл изображения по умолчанию.
Сначала скопируйте 2 изображения flower.png & image-not-found.png в папках images на вашем WebApp.
 
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();
 
        // The path to the root directory of the webapp (WebContent)
        String realRootPath = request.getServletContext().getRealPath("");

  
        // Real path of Image.
        String imageRealPath = realRootPath + servletPath;

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

        File file = new File(imageRealPath);

 
        // Check image exists.
        if (file.exists()) {

      
            // Go to the next element (filter or servlet) in chain
            chain.doFilter(request, response);

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

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

        }

    }

}
Перезапустите ваше приложение и запустите следующие URL:
С ссылкой запроса изображения, а изображение не существует, то она будет перенаправлена к изображению по умолчанию.
В приведенном выше примере вы можете также использовать пересылка (Forward) вместо того, чтобы перенаправить (Redirect) запрос на изображение по умолчанию в случае, если запрошенное изображение, не существует.
// Redirect:

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

// Forward:

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

11- Установка соединения JDBC в Filter

Вы можете создать объект Connection соединить с   JDBC  в Servlet  для обработки с Database. Но вы также можете создать объект Connection соединить с  JDBC в Filter, и он будет работать с разными Servlet. И вы можете использовать этот  Connection на протяжении всего пути запроса. Чтобы легче было понять, вы можете посмотреть на иллюсстрацию ниже:
ConnectionUtils это класс, который создает объект  Connection, соединяя к database, в данной статье я буду детально знакомить со способом, как вы получаете объект Connection.
Вы можете просмотреть статью  JDBC по ссылке:
ConnectionUtils.java
package org.o7planning.tutorial.servletfilter.conn;

import java.sql.Connection;

public class ConnectionUtils {

   public static Connection getConnection() {
     
       // Create a Connection to 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";

   // Store Connection to attribute of request
   // Information stored only exist during requests
   // until the data is returned to the user browser.
   public static void storeConnection(ServletRequest request, Connection conn) {
       request.setAttribute(ATT_NAME, conn);
   }

   // Get the Connection object has been stored in one attribute of the request.
   public static Connection getStoredConnection(ServletRequest request) {
       Connection conn = (Connection) request.getAttribute(ATT_NAME);
       return conn;
   }
}
Я объявляю  URL-pattern для JDBCFilter это  /*, этот filter будет работать со всеми запросами пользователей.
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();
       
       //
       // Only open Connection for special request.
       // (For example: servlet, jsp, ..)
       //
       // Avoid open connection for the common request
       // (For example image, css, javascript,... )
       //        
       if (servletPath.contains("/specialPath1") || servletPath.contains("/specialPath2")) {
           Connection conn = null;
           try {
           
               // Create a Connection
               conn = ConnectionUtils.getConnection();
         
               // Set auto commit false
               conn.setAutoCommit(false);

       
               // Store connection in attribute of request.
               MyUtils.storeConnection(request, conn);

         
               // Go to next element (filter or target) in chain
               chain.doFilter(request, response);

       
               // Call commit() to commit transaction.
               conn.commit();
           } catch (Exception e) {
               ConnectionUtils.rollbackQuietly(conn);
               throw new ServletException();
           } finally {
               ConnectionUtils.closeQuietly(conn);
           }
       }
   
       // For common request.
       else {
       
           // Go to next element (filter or target) in chain.
           chain.doFilter(request, response);
       }

   }

}
В следующем Filter или в servlet или JSP-странице (в одном запросе), вы можете получить объект Connection, который сохранился в атрибуте (attribute) запроса:
Connection conn = (Connection) request.getAttribute(ATT_NAME);

// Or

Connection conn = MyUtils.getStoredConnection();

12- Руководство программирования JSP

Далее вы можете изучить про JSP: