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

  • 相关阅读:
    53. Maximum Subarray
    64. Minimum Path Sum
    28. Implement strStr()
    26. Remove Duplicates from Sorted Array
    21. Merge Two Sorted Lists
    14. Longest Common Prefix
    7. Reverse Integer
    412. Fizz Buzz
    linux_修改域名(centos)
    linux_redis常用数据类型操作
  • 原文地址:https://www.cnblogs.com/zhanghaiyang/p/7212725.html
Copyright © 2011-2022 走看看