zoukankan      html  css  js  c++  java
  • Java Web学习总结(29)——Java Web中的Filter和Interceptor比较

    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;            
                   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是一致的。

  • 相关阅读:
    typescript 深层次对象内层(N)转外层(N),支持多层级递归转换,多应用于多语言数据结构转换
    js 柯里化、深拷贝、浅拷贝
    js IntersectionObserver api
    javascript-state-machine
    NodeJs的CommonJS模块规范
    js 动画
    【THUPC 2018】赛艇
    【CF528D】Fuzzy Search
    【UR #6】懒癌
    【HNOI 2019】JOJO
  • 原文地址:https://www.cnblogs.com/zhanghaiyang/p/7212725.html
Copyright © 2011-2022 走看看