對于Java Web中的Filter和Interceptor的理解

Ellie30M 8年前發布 | 27K 次閱讀 MVC模式 Servlet Java開發

寫在前面:因為部門項目中有用戶登錄驗證方面的需求,故而學習了一下相關的驗證技術,本文僅是作者個人學習的心得,由于水平有限,如有錯誤之處還請指出、見諒。

1. 背景

在設計web應用的時候,用戶登錄/注冊是必不可少的功能,對用戶登錄信息進行驗證的方法也是多種多樣,大致可以認為如下模式:前端驗證+后臺驗證。根據筆者的經驗,一般會在前端進行一些例如是否輸入數據、輸入的數據的格式是否正確等一系列的驗證,在后臺會查詢數據庫進行驗證。

一般在后臺進行驗證的時候,都會選擇使用Servlet的Filter作為攔截器,本文主要介紹Servlet的Filter,然后再拿它跟Spring MVC的HnadlerInterceptor進行對比。

2. Filter

2.1 什么是Filter

Servlet作為Java Web的基礎,它的一個比較核心也被廣泛應用的功能就是Filter,又叫攔截器。顧名思義,攔截器就是起到攔截作用的。一般情況下,用戶從客戶端發出請求到服務器后,整個的流程是:

HttpRequest ----> Filter ----> Servlet ----> Controller/Action/... ----> Filter ----> HttpResponse

根據上面的流程可以看出,Filter的作用就是在用戶請求到達Servlet之前,進行攔截。在攔截到用戶的請求后,我們可以實現一些自定義的業務邏輯,例如之前說到的對用戶登錄信息進行驗證。Filter還可以在服務器響應到達客戶端之前對響應的數據進行修改,本文主要介紹第一個功能。

2.2 Filter的工作原理

Filter跟Servlet一樣都是由服務器負責創建和銷毀的,在web應用程序啟動時,服務器會根據應用程序的web.xml文件中的配置信息調用 public void init(FilterConfig filterConfig) throws ServletException 方法來初始化Filter,在web應用程序被移除或者是服務器關閉時,會調用 public void destroy() 來銷毀Filter。在一個應用程序中一個Filter只會被創建和銷毀一次,在進行完初始化之后,Filter中聲明了 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException 方法,用來實現一些需要在攔截完成之后的業務邏輯。

注意到上面的 doFilter() 方法的參數中,有 chain 這個參數,它是傳遞過來的攔截鏈對象,里面包含了用戶定義的一系列的攔截器,這些攔截器根據其在web.xml中定義的順序依次被執行。當用戶的信息驗證通過或者當前攔截器不起作用時,我們可以執行 chain.doFilter() 方法來跳過當前攔截器來執行攔截器鏈中的下一個攔截器。

2.3 自己實現Filter

自己實現Filter時,需要繼承接口 javax.servlet.Filter 并且實現相關的方法。

2.3.1所用到的工具:

IDE: IntelliJ IDEA

構建工具:gradle

本地服務器:Tomcat

2.3.2 具體代碼

build.gradle

group 'xiangang.wei'
version '1.0-SNAPSHOT'

apply plugin: 'java'
apply plugin: 'war'

sourceCompatibility = 1.8

repositories {   
    jcenter()    
    mavenCentral()
}
dependencies {   
    testCompile group: 'junit', name: 'junit', version: '4.11'    
    // servlet-api    
    compile group: 'javax.servlet', name: 'servlet-api', version: '2.5'
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"               
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"           
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee        
                    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">    
<filter>        
  <filter-name>loginValidation</filter-name>      
  <filter-class>filter.LoginValidation</filter-class>
  <init-param>            
    <param-name>redirectPath</param-name>            
    <param-value>/index.jsp</param-value>        
  </init-param>        
  <init-param>            
    <param-name>disableloginValidation</param-name>            
    <param-value>N</param-value>        
  </init-param>        
  <init-param>            
    <param-name>logonString</param-name>            
    <param-value>/index.jsp</param-value>        
  </init-param>    
  </filter>    
  <filter-mapping>        
    <filter-name>loginValidation</filter-name>        
    <url-pattern>/*</url-pattern>   
   </filter-mapping>
</web-app>

web.xml中<init-param>標簽被用來配置Filter的初始化時使用的參數,其中<param-name>標簽表示參數的名字,可以是自己定義的任何名字,<param-value>標簽表示對應的初始化參數的值。上面的初始化參數中, redirectPath 定義了當驗證不成功時頁面重定向的的路徑, logonString 定義了攔截器攔截的指定URL。 <filter-mapping> 標簽定義了攔截器的攔截模式,在 <url-pattern> 標簽定義了攔截模式,上面的 /* 表示攔截所有。它和之前定義的指定攔截的URL標簽結合起來使用。

index.jsp

<%--  
Created by IntelliJ IDEA.  
User: xiangang  
Date: 2016/11/21  
Time: 下午3:42  
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>    
    <title>用戶登陸界面</title>   
    <style type="text/css">        
      div{            
              margin: auto;            
              border: gray 1px solid;            
              width: 70%;        
          }    
    </style>
    </head>
    <body>
      <div>    
        <form action="success.jsp" method="post">       
         <table>            
          <tr>                
            <td>用戶名:<input type="text" name="name" /></td>            
          </tr>            
          <tr>                
            <td>密   碼:<input type="password" name="password" /></td>            
          </tr>            
          <tr>                
            <td><input type="submit" value="提交" /></td>                
            <td><input type="reset" value="重置" /></td>            
          </tr>        
        </table>    
      </form>
    </div>
  </body>
</html>

Filter對應的實現類:

LoginValidation.java

package filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/** * 
Created by xiangang on 2016/11/21. 
*/

public class LoginValidation implements Filter{    
public FilterConfig config;    
public static boolean isContains(String url, String[] regx){        
boolean flag = false;        
for (int i = 0;i<regx.length;i++){            
  if (url.indexOf(regx[i])!=-1){                
    flag = true;                
    return flag;            
  }        
}        
  return flag;    
}    

@Override    
public void init(FilterConfig filterConfig) throws ServletException {        
  this.config = filterConfig;    
}    

@Override    
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {        
  HttpServletRequest httpServletRequest= (HttpServletRequest)request;        
  HttpServletResponse httpServletResponse = (HttpServletResponse)response;        
  String name = httpServletRequest.getParameter("name");        
  String password = httpServletRequest.getParameter("password");        
  String redirectPath=httpServletRequest.getContextPath()+config.getInitParameter("redirectPath");        
  String logonString = config.getInitParameter("logonString");
  String[] logonList = logonString.split(";");    

  if (isContains(httpServletRequest.getRequestURI(),logonList)){            
    chain.doFilter(request,response);            
    return;        
  }
if ("Y".equals(config.getInitParameter("disableloginValidation"))){            
    chain.doFilter(request,response); 
    return;        
  }
if ("root".equals(name) && "admin".equals(password)){            
    chain.doFilter(request,response);
    return;
  }else{ 
    httpServletResponse.sendRedirect(redirectPath); 
    return; 
   } 
  }   

@Override    public void destroy() {
  this.config = null;    
  }
}

登錄成功之后的頁面:

success.jsp

<%--  
Created by IntelliJ IDEA.  
User: xiangang  
Date: 2016/11/21  
Time: 下午3:56  
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>    
<title>登錄成功!</title>
</head>
<body>
歡迎!
</body>
</html>

配置好Tomcat之后,開啟應用程序:

登錄失敗時不會跳轉,仍然會停留在原頁面。

3. Interceptor

之前提到的Filter是Servlet層面的攔截器,在許多的Java Web框架中,都實現了自己的攔截器Interceptor。例如Struts2中的Interceptor、Spring MVC中的HandlerInterceptor等。相比于Filter,框架中的Interceptor的產生作用的時間和位置不一樣,下面描述了應用了Spring MVC中的HandlerInterceptor的web請求流程:

HttpRequest ----> DispactherServlet ----> HandlerInterceptor ---->Controller----> HandlerInterceptor ----> HttpResponse

兩者的主要區別在于Filter起作用的時機是在請求到達Servlet之前,二HandlerInterceptor其作用的時機是在DispactherServlet接收到用戶請求完成請求到相應的Handler映射之后。雖然都先于在具體的業務邏輯執行,但是還是存在一些差異。Filter面對的是所有的請求,而HandlerInterceptor是面對具體的Controller。Filter總是先于HandlerInterceptor發揮作用,在Filter中甚至可以中斷請求,從而使它無法到達相應的Servlet。而且兩者的配置也不一樣,Filter是在web.xml中進行配置,HandlerInterceptor是在具體的applicationContext.xml中進行配置。

3.1 HandlerInterceptor的工作原理

分析源碼,發現HandlerInterceptor接口中聲明了如下幾個方法:

public interface HandlerInterceptor {  

/**    
* Intercept the execution of a handler. Called after HandlerMapping determined
* an appropriate handler object, but before HandlerAdapter invokes the handler.    
* <p>DispatcherServlet processes a handler in an execution chain, consisting    
* of any number of interceptors, with the handler itself at the end.    
* With this method, each interceptor can decide to abort the execution chain,    
* typically sending a HTTP error or writing a custom response.    
* <p><strong>Note:</strong> special considerations apply for asynchronous    
* request processing. For more details see    
* {@link org.springframework.web.servlet.AsyncHandlerInterceptor}.    
* @param request current HTTP request    
* @param response current HTTP response    
* @param handler chosen handler to execute, for type and/or instance evaluation    
* @return {@code true} if the execution chain should proceed with the    
* next interceptor or the handler itself. Else, DispatcherServlet assumes    
* that this interceptor has already dealt with the response itself.    
* @throws Exception in case of errors    
*/   

boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;  

/**   
* Intercept the execution of a handler. Called after HandlerAdapter actually    
* invoked the handler, but before the DispatcherServlet renders the view.    
* Can expose additional model objects to the view via the given ModelAndView.    
* <p>DispatcherServlet processes a handler in an execution chain, consisting    
* of any number of interceptors, with the handler itself at the end.    
* With this method, each interceptor can post-process an execution,    
* getting applied in inverse order of the execution chain.    
* <p><strong>Note:</strong> special considerations apply for asynchronous    * request processing. For more details see    
* {@link org.springframework.web.servlet.AsyncHandlerInterceptor}.    
* @param request current HTTP request    
* @param response current HTTP response    
* @param handler handler (or {@link HandlerMethod}) that started asynchronous    
* execution, for type and/or instance examination    
* @param modelAndView the {@code ModelAndView} that the handler returned    
* (can also be {@code null})    
* @throws Exception in case of errors    
*/   

void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception;   

/**    
* Callback after completion of request processing, that is, after rendering    
* the view. Will be called on any outcome of handler execution, thus allows    
* for proper resource cleanup.   
* <p>Note: Will only be called if this interceptor's {@code preHandle}    
* method has successfully completed and returned {@code true}!    
* <p>As with the {@code postHandle} method, the method will be invoked on each    
* interceptor in the chain in reverse order, so the first interceptor will be    
* the last to be invoked.    
* <p><strong>Note:</strong> special considerations apply for asynchronous    
* request processing. For more details see    
* {@link org.springframework.web.servlet.AsyncHandlerInterceptor}.    
* @param request current HTTP request    
* @param response current HTTP response    
* @param handler handler (or {@link HandlerMethod}) that started asynchronous    
* execution, for type and/or instance examination    
* @param ex exception thrown on handler execution, if any    
* @throws Exception in case of errors    
*/   

void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception;

}

這三個方法分別會在具體的HandlerController方法執行之前,執行成功之后,和執行完成之后被執行。

3.2 自己實現HandlerInterceptor

Spring MVC框架采用了適配器的開發模式,使用一個抽象的類 HandlerInterceptorAdapter 實現 HandlerInterceptor 接口,這樣當我們需要自己實現HandlerInterceptor時,我們可以繼承 HandlerInterceptorAdapter 這樣我們就不用全部實現這三個方法,而可以選擇性的實現自己需要的方法。

3.2.1所用到的工具:

IDE: IntelliJ IDEA

構建工具:gradle

本地服務器:Tomcat

3.2.2具體的代碼:

build.gradle

group 'xiangang.wei'
version '1.0-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'war'source

Compatibility = 1.8

repositories {    
jcenter()    
mavenCentral()
}

dependencies {    
testCompile group: 'junit', name: 'junit', version: '4.11'    
// servlet-api    
compile group: 'javax.servlet', name: 'servlet-api', version: '2.5'    
//spring相關    
compile group: 'org.springframework', name: 'spring-webmvc', version: '4.3.3.RELEASE'    
compile group: 'org.springframework', name: 'spring-orm', version: '4.3.3.RELEASE'    
compile group: 'org.springframework', name: 'spring-aspects', version: '4.3.3.RELEASE'    
compile group: 'org.springframework.security', name: 'spring-security-config', version: '3.2.0.RELEASE'    
compile group: 'org.springframework.security', name: 'spring-security-taglibs', version: '3.2.0.RELEASE'    
compile 'org.springframework.security:spring-security-web:3.2.0.RELEASE'    //hibernate相關    
compile 'org.hibernate:hibernate-core:4.3.6.Final'    
//mysql    
compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.39'    
//springData    
compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '1.10.3.RELEASE'    
// https://mvnrepository.com/artifact/log4j/log4j日志    
compile group: 'log4j', name: 'log4j', version: '1.2.17'   
//json解析相關    
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.5.4'    
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.5.4'
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"           
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"           
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee        
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"           version="3.0">    
<!--框架默認幫我們配置好了ApplicationContext的實現類(org.springframework.web.context.support.XmlWebApplicationContext),不需要自己手動配置-->    

<!--配置ApplicationContext需要加載的配置文件-->    
<context-param>        
<param-name>contextConfigLocation</param-name>       
<param-value>classpath:databaseAccess.xml,classpath:service.xml</param-value>    
</context-param>    

<!--ApplicationContext的加載和關閉-->    
<listener>        
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>    
</listener>    
<!--配置字符過濾器,防止出現中文亂碼-->    
<filter>        
<filter-name>CharacterEncodingFilter</filter-name>        
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>        
<init-param>            
<param-name>encoding</param-name>            
<param-value>utf-8</param-value>        
</init-param>    
</filter>    
<filter-mapping>        
<filter-name>CharacterEncodingFilter</filter-name>        
<url-pattern>/*</url-pattern>    
</filter-mapping>    

<!--配置Spring MVC的前置控制器-->    
<servlet>        
<servlet-name>dispatcherServlet</servlet-name>        
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>        
<init-param>            
<param-name>contextConfigLocation</param-name>            
<param-value>classpath:dispatcherServlet.xml</param-value>        
</init-param>        
<load-on-startup>1</load-on-startup>    
</servlet>    
<servlet-mapping>        
<servlet-name>dispatcherServlet</servlet-name>        
<url-pattern>/</url-pattern>    
</servlet-mapping>
</web-app>

涉及到HandlerInterceptor的配置文件 dispatcherServlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"       
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       
xmlns:context="http://www.springframework.org/schema/context"       
xmlns:mvc="http://www.springframework.org/schema/mvc"       
xsi:schemaLocation="http://www.springframework.org/schema/beans       
http://www.springframework.org/schema/beans/spring-beans.xsd       
http://www.springframework.org/schema/context       
http://www.springframework.org/schema/context/spring-context.xsd       
http://www.springframework.org/schema/mvc       
http://www.springframework.org/schema/mvc/spring-mvc.xsd">    

<!--開啟注解-->    
<mvc:annotation-driven/>    

<!--添加需要掃描的包-->    
<context:component-scan base-package="ims" use-default-filters="false">        
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>    
</context:component-scan>    

<!--添加HandlerInterceptor-->    
<mvc:interceptors>        
<mvc:interceptor>            
<mvc:mapping path="/user/**"/>            
<bean class="ims.handlerInterceptor.LoginInterceptor"/>        
</mvc:interceptor>        
<mvc:interceptor>            
<mvc:mapping path="/register/**"/>            
<bean class="ims.handlerInterceptor.RegisterInterceptor"/>        
</mvc:interceptor>    
</mvc:interceptors>    

<!--添加試圖解析器-->    
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">        
<property name="prefix" value="/WEB-INF/views/"/>        
<property name="suffix" value=".jsp"/>    
</bean>

</beans>

配置HandlerInterceptor有兩種方式,一種是如上面所示,這張方式配置的HandlerInterceptor可以指定具體的攔截路徑,另外一種方式是直接在 <mvc:interceptors> 中使用<bean>標簽進行配置: <bean class="ims.handlerInterceptor.LoginInterceptor"/> 按照這種方式配置的HandlerInterceptor會對所有的路徑進行攔截。

具體的Interceptor實現類:

LoginInterceptor.java

package ims.handlerInterceptor;
import ims.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/** 
* Created by xiangang on 16/11/17. 
*/

public class LoginInterceptor extends HandlerInterceptorAdapter {    

@Autowired    
private UserService userService;   

@Override    
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        
  boolean flag = true;        
  HttpSession session = request.getSession();        
  if (session.getAttribute("user") == null) {           
  String userName = request.getParameter("userName");            
  String password = request.getParameter("password");            
  flag = userService.selectByUserName(userName, password);            
  if (flag){                
    session.setAttribute("user",userName);            
    }        
  }        
  if (!flag){            
    response.sendRedirect("index.jsp");        
    }        
  return flag;   
  }
}

具體的運行結果這里也就不再貼圖了,實現的效果跟之前的Filter是一致的。

 

 

來自:http://www.jianshu.com/p/39c0cfe25997

 

 本文由用戶 Ellie30M 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!