【LWei-开发】Java过滤器与拦截器的区别及使用

一、区别

过滤器:是在javaweb中,你传入的request、response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者struts的action进行业务逻辑,比如过滤掉非法url(不是login.do的地址请求,如果用户没有登陆都过滤掉),或者在传入servlet或者 struts的action前统一设置字符集,或者去除掉一些非法字符.。【当有一堆东西的时候,只希望选择符合要求的某一些东西。定义这些要求的工具,就是过滤器。(理解:就是一堆字母中取一个B)】

拦截器 :是在面向切面编程的就是在你的service或者一个方法,前调用一个方法,或者在方法后调用一个方法比如动态代理就是拦截器的简单实现,在你调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在你调用方法后打印出字符串,甚至在你抛出异常的时候做业务逻辑的操作。【在一个流程正在进行的时候,你希望干预它的进展,甚至终止它进行,这是拦截器做的事情。(理解:就是一堆字母中,干预他,通过验证的少点,顺便干点别的东西)

过滤器 拦截器
基于函数回调 基于java的反射机制
依赖于servlet容器 不依赖于servlet容器
对几乎所有的请求起作用 只能对action请求起作用
不能访问action上下文、值栈里的对象 可以访问action上下文、值栈里的对象
只能在容器初始化时被调用一次,当web应用停止或重新部署的时候才销毁 在action的生命周期中,拦截器可以多次被调用
分类 过滤器 拦截器
多个的执行顺序 根据filter mapping配置的先后顺序 按照配置的顺序,但是可以通过order控制顺序
规范 在Servlet规范中定义的,是Servlet容器支持的 spring容器内的,是Spring框架支持的。
使用范围 只能用于Web程序中 既可以用于Web程序,也可以用于Application、Swing程序中。
深度 Filter在只在Servlet前后起作用 拦截器能够深入到方法前后、异常抛出前后等
灵活性 要是针对URL地址做一个编码的事情、过滤掉没用的参数、安全校验(比较泛的,比如登录不登录之类) 功能更强大些,Filter能做的事情,他都能做,而且可以在请求前,请求后执行,比较灵活。
配置 只能通过web.xml配置。 拦截器也是一个Spring的组件,归Spring管理,配置在Spring文件中,因此能使用Spring里的任何资源、对象,例如Service对象、数据源、事务管理等,通过IoC注入到拦截器即可;
作用深度 Filter在只在Servlet前后起作用。 拦截器能够深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。所以在Spring构架的程序中,要优先使用拦截器。

二、使用

1、过滤器

过滤器是处于客户端与服务器资源文件之间的一道过滤网,在访问资源文件之前,通过一系列的过滤器对请求进行修改、判断等,把不符合规则的请求在中途拦截或修改。也可以对响应进行过滤,拦截或修改响应。

过滤器一般用于登录权限验证、资源访问权限控制、敏感词汇过滤、字符编码转换等等操作,便于代码重用,不必每个servlet中还要进行相应的操作。

如图,浏览器发出的请求先递交给第一个filter进行过滤,符合规则则放行,递交给filter链中的下一个过滤器进行过滤。过滤器在链中的顺序与它在web.xml中配置的顺序有关,配置在前的则位于链的前端。当请求通过了链中所有过滤器后就可以访问资源文件了,如果不能通过,则可能在中间某个过滤器中被处理掉。

在doFilter()方法中,chain.doFilter()前的一般是对request执行的过滤操作,chain.doFilter后面的代码一般是对response执行的操作。过滤链代码的执行顺序如下:

(1)新建一个class,实现接口Filter(注意:是javax.servlet中的Filter)
(2)重写过滤器的doFilter(request,response,chain)方法。另外两个init()、destroy()方法一般不需要重写。在doFilter方法中进行过滤操作。
常用代码有:获取请求、获取响应、获取session、放行。

HttpServletRequest request=(HttpServletRequest) arg0;//获取request对象
HttpServletResponse response=(HttpServletResponse) arg1;//获取response对象
HttpSession session=request.getSession();//获取session对象
chain.doFilter(request, response);//放行,通过了当前过滤器,递交给下一个filter进行过滤

(3)在web.xml中配置过滤器。这里要谨记一条原则:在web.xml中,监听器>过滤器>servlet。也就是说web.xml中监听器配置在过滤器之前,过滤器配置在servlet之前,否则会出错。

<filter>  
    <filter-name>loginFilter</filter-name>//过滤器名称  
    <filter-class>com.ygj.control.loginFilter</filter-class>//过滤器类的包路径
<init—param> //可选
    <param—name>参数名</param-name>//过滤器初始化参数
    <param-value>参数值</param-value>  
</init—pamm>  
</filter>
 
<filter-mapping>//过滤器映射  
    <filter-name>loginFilter</filter-name>  
<url—pattern>指定过滤器作用的对象</url-pattern>

在配置中需要注意的有两处:一是<filter-class>指明过滤器类所在的包路径。二是<url-pattren>处定义过滤器作用的对象。一般有以下规则:
A.作用于所有web资源:<url—pattern>/*</url-pattern>。则客户端请求访问任意资源文件时都要经过过滤器过滤,通过则访问文件,否则拦截。
B.作用于某一文件夹下所有文件:<url—pattern>/dir/*</url-pattern>
C.作用于某一种类型的文件:<url—pattern>*.扩展名</url-pattern>。比如<url—pattern>*.jsp</url-pattern>过滤所有对jsp文件的访问请求。
D.作用于某一文件夹下某一类型文件:<url—pattern>/dir/*.扩展名</url-pattern>
如果一个过滤器需要过滤多种文件,则可以配置多个<filter-mapping>,一个mapping定义一个url-pattern来定义过滤规则。

2、拦截器

作用:拦截器是web项目不可或缺的组成部分,一般使用拦截器实现以下功能:
(1)登录session验证,防止浏览器端绕过登录,直接进入到应用。或者session超时后,返回到登录页面。
(2)记录系统日志,一个完善的应用系统,应该具备监控功能,通过完善的系统日志记录系统运行过程中都经历了什么,当发生错误的时候及时通知管理人员,将损失降到最低。同时通过系统日志的监控,也能监控每次访问的响应时长,作为性能调优的参考。
(3)对请求进行前置或后置的操作,比如对于服务端返回的异常信息,可以通过拦截器统一的进行后处理,使其格式统一。

实现方式

(1)基于Spring AOP 的切面方式
(2)基于Servlet规范的拦截器

示例

实现功能权限校验的功能有多种方法,其一使用拦截器拦截请求,其二是使用AOP抛异常。
首先用拦截器实现未登录时跳转到登录界面的功能。注意这里没有使用AOP切入,而是用拦截器拦截,因为AOP一般切入的是service层方法,而拦截器是拦截控制器层的请求,它本身也是一个处理器,可以直接中断请求的传递并返回视图,而AOP则不可以。

1.使用拦截器实现未登录时跳转到登录界面的功能

# 1.1 拦截器SecurityInterceptor
public class SecurityInterceptor implements HandlerInterceptor{

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("SecurityInterceptor:"+request.getContextPath()+","+request.getRequestURI()+","+request.getMethod());
        HttpSession session = request.getSession();
        if (session.getAttribute(Helper.SESSION_USER) == null) {
            System.out.println("AuthorizationException:未登录!"+request.getMethod());
            if("POST".equalsIgnoreCase(request.getMethod())){
                response.setContentType("text/html; charset=utf-8");  
                PrintWriter out = response.getWriter();   
                out.write(JSON.toJSONString(new Result(false,"未登录!")));
                out.flush();
                out.close();
            }else{
                response.sendRedirect(request.getContextPath()+"/login"); 
            }
            return false;
        } else {
            return true;
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        // TODO Auto-generated method stub

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        // TODO Auto-generated method stub
    }

}
# 1.2.spring-mvc.xml(拦截器配置部分)
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
    <mvc:resources mapping="/resources/**" location="/resources/" />
<mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/*"/>  <!-- 拦截/  /test  /login  等等单层结构的请求  --> 
            <mvc:mapping path="/**/*.aspx"/><!-- 拦截后缀为.aspx的请求 -->
            <mvc:mapping path="/**/*.do"/><!-- 拦截后缀为 .do的请求 -->
            <mvc:exclude-mapping path="/login"/>
            <mvc:exclude-mapping path="/signIn"/>
            <mvc:exclude-mapping path="/register"/>
            <bean class="com.demo.filter.SecurityInterceptor">
            </bean>
        </mvc:interceptor>
    </mvc:interceptors>

# 特别说明:拦截器拦截的路径最好是带有后缀名的,否则一些静态的资源文件不好控制,也就是说请求最好有一个统一的格式如 .do 等等,这样匹配与过滤速度会非常快。如果不这样,例如 用 /** 来拦截所有的请求,则页面渲染速度会非常慢,因为资源文件也被拦截了。

2.使用AOP实现功能权限校验

对于功能权限校验也可以类似地用拦截器来实现,只不过会拦截所有的请求,对不需要权限校验的请求没有很好的过滤功能,所以采用AOP指定拦截需要校验的方法的方式来实现之。

# 2.1 切面类 PermissionAspect
/**
 * 事件日志 切面,凡是带有 @ValidatePermission 以及@ResponseBody注解 控制器 都要进行 功能权限检查,
 * 若无权限,则抛出AccessDeniedException 异常,该异常将请求转发至一个控制器,然后将异常结果返回
 * @author Administrator
 *
 */
public class PermissionAspect {
    @Autowired
    SysUserRolePermService sysUserRolePermService;

    public void doBefore(JoinPoint jp) throws IOException{
        System.out.println(
                "log PermissionAspect Before method: " + jp.getTarget().getClass().getName() + "." + jp.getSignature().getName());
        Method soruceMethod = getSourceMethod(jp);
        if(soruceMethod!=null){
            ValidatePermission oper = soruceMethod.getAnnotation(ValidatePermission.class);
            if (oper != null) {
                int fIdx = oper.idx();
                Object[] args = jp.getArgs();
                if (fIdx>= 0 &&fIdx<args.length){
                    int functionId = (Integer) args[fIdx];
                    String rs = sysUserRolePermService.permissionValidate(functionId);
                    System.out.println("permissionValidate:"+rs);
                    if(rs.trim().isEmpty()){
                        return ;//正常
                    }
                }
            }
        }
        throw new AccessDeniedException("您无权操作!");
    }
    private Method getSourceMethod(JoinPoint jp){
        Method proxyMethod = ((MethodSignature) jp.getSignature()).getMethod();
        try {
            return jp.getTarget().getClass().getMethod(proxyMethod.getName(), proxyMethod.getParameterTypes());
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        return null;
    }
}
# 2.2自定义注解ValidatePermission
/**
 * @Descrption该注解是标签型注解,被此注解标注的方法需要进行权限校验
 */
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface ValidatePermission {
    /**
     * @Description功能Id的参数索引位置  默认为0,表示功能id在第一个参数的位置上,-1则表示未提供,无法进行校验
     */
    int idx() default 0;
}

# 说明: AOP切入的是方法,不是某个控制器请求,所以不能直接返回视图来中断该方法的请求,但可以通过抛异常的方式达到中断方法执行的目的,所以在before通知中,如果通过验证直接return返回继续执行连接点方法,否则抛出一个自定义异常AccessDeniedException来中断连接点方法的执行。该异常的捕获可以通过系统的异常处理器(可以看做控制器)来捕获并跳转到一个视图或者一个请求。这样就达到拦截请求的目的。所以需要配置异常处理器。
# 2.3 spring-mvc.xml(异常处理器配置,以及aop配置)
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <!-- <property name="defaultErrorView" value="rediret:/error"></property>   -->
        <property name="exceptionMappings">
            <props>
                <!--<prop key="com.demo.exception.AuthorizationException">redirect:/login</prop>-->
                <prop key="com.demo.exception.AccessDeniedException">forward:/accessDenied</prop>
            </props>
        </property>
    </bean>
<bean id="aspectPermission" class="com.demo.filter.PermissionAspect" />
    <!-- 对带有@ValidatePermission和ResponseBody注解的controller包及其子包所有方法执行功能权限校验  --> 
    <aop:config proxy-target-class="true">  
        <aop:aspect ref="aspectPermission">  
            <aop:pointcut id="pc"  
                expression="@annotation(com.demo.annotation.ValidatePermission) 
                and @annotation(org.springframework.web.bind.annotation.ResponseBody) 
                and execution(* com.demo.controller..*.*(..)) " />  
            <aop:before pointcut-ref="pc" method="doBefore"/>  
        </aop:aspect>  
    </aop:config>
# 2.4 异常处理器将请求转发到的控制器请求 forward:/accessDenied
@RequestMapping(value = "/accessDenied",produces = "text/html;charset=UTF-8")
@ResponseBody
public String accessDenied(){
    return JSON.toJSONString(new Result(false,"您没有权限对此进行操作!"));
}
# 2.5 请求校验不通过时 由上述的控制器返回结果本身
{"info":"您没有权限对此进行操作!","success":false}
# 2.6 功能校验service示例
/**
     * 校验当前用户在某个模块的某个功能的权限
     * @param functionId
     * @return 空字符串表示 有权限 ,否则是错误信息
     * @throws Exception 
     */
    public String permissionValidate(int functionId){
        Object o =  request.getSession().getAttribute(Helper.SESSION_USER);
        //if(o==null)  throw new AuthorizationException(); 
        SysUser loginUser= (SysUser)o;
        if(loginUser.getUserid() == 1) return "";
        try{
            return mapper.permissionValidate(loginUser.getUserid(),functionId);
        }catch(Exception ex){
            ex.printStackTrace();
            return "数据库操作出现异常!";
        }
    }

# 说明: 这里仅仅是对带有@ValidatePermission和@ResponseBody注解的controller包及其子包所有方法进行切入,这样肯定是不够通用的,应该是对带有@ValidatePermission的方法进行切入,在切面类中通过判断该方法是否有@ResponseBody注解来抛出不一样的异常,若带有@ResponseBody注解则抛出上述的异常返回json字符串,否则,应该抛出另一个自定义异常然后将请求重定向到一个合法的视图如error.jsp .

# 通过客户端发送 /moduleAccess.do 请求,该请求对应的方法同时具有@ValidatePermission和@ResponseBody,并且有功能Id参数fid,这样AOP可以切入该方法,执行doBefore通知,通过功能参数fid,对它结合用户id进行权限校验,若校验通过直接返回,程序继续执行,否则抛出自定义异AccessDeniedException,该异常由系统捕获(需要配置异常处理器)并发出请求 forward:/accessDenied ,然后对应的控制器 /accessDenied 处理该请求返回一个包含校验失败信息的json给客户端。这样发送 /moduleAccess.do 请求,如果校验失败,转发到了/accessDenied请求,否则正常执行。绕了这么一个大圈子才实现它。

猜你喜欢

转载自blog.csdn.net/u013153374/article/details/84877539
今日推荐