springMVC拦截器

本文介绍了SpringMVC拦截器的实现原理、实现方式和HandlerInterceptor接口的方法。拦截器用于用户认证、权限拦截和性能监控,依赖于SpringMVC框架,基于Java反射和AOP。讲解了自定义拦截器的两种方式,并详细阐述了preHandle、postHandle和afterCompletion三个方法的作用。此外,还提到了拦截器适配器HandlerInterceptorAdapter以及在实际应用中配置和使用拦截器时的注意事项,如处理编码、避免循环登录问题。最后对比了拦截器与过滤器的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、     SpringMVC拦截器实现原理

用户请求到DispatherServlet中,DispatherServlet调用HandlerMapping查找Handler,HandlerMapping返回一个拦截器链(HandlerExecutionChain),springmvc中的拦截器是通过HandlerMapping发起的。

         在企业开发,使用拦截器实现用户认证(用户登陆后进行身份校验拦截),用户权限拦截和方法性能监控等。

       依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上基于Java的反射机制,属于面向切面编程AOP)的一种运用。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理


二、     springMVC拦截器的实现方式

第一种方式是要定义的Interceptor类要实现了Spring的HandlerInterceptor 接口

第二种方式是继承实现了HandlerInterceptor接口的类,比如Spring已经提供的实现了HandlerInterceptor接口的抽象类HandlerInterceptorAdapter

三、     HandlerInterceptor接口的方法说明

HandlerInterceptor接口中定义了三个方法,我们就是通过这三个方法来对用户的请求进行拦截处理的。

对于过个拦截器它们之间的执行顺序跟在SpringMVC的配置文件中定义的先后顺序有关

  preHandle(): 这个方法在业务处理器处理请求之前被调用,SpringMVC 中的Interceptor是链式调用的,在一个应用中或者说是在一个请求中可以同时存在多个Interceptor。每个Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是Interceptor中的preHandle 方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔值Boolean 类型的,当它返回为false 时,表示请求结束,后续的Interceptor 和Controller都不会再执行;当返回值为true 时就会继续调用下一个Interceptor 的preHandle 方法,如果已经是最后一个Interceptor 的时候就会是调用当前请求的Controller 方法。

     postHandle():这个方法在当前请求进行处理之后,也就是Controller方法调用之后执行,但是它会在DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller处理之后的ModelAndView 对象进行操作。postHandle 方法被调用的方向跟preHandle 是相反的,也就是说先声明的Interceptor postHandle 方法反而会后执行。

     afterCompletion():该方法也是需要当前对应的Interceptor 的preHandle 方法的返回值为true 时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在DispatcherServlet渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作的。afterCompletion方法被调用的方向和perHandle也是相反的,先声明的InterceptorafterCompletion方法后执行。

拦截器适配器

有时候我们可能只需要实现三个回调方法中的某一个,如果实现HandlerInterceptor接口的话,三个方法必须实现,不管你需不需要,此时spring提供了一个HandlerInterceptorAdapter适配器(一种适配器设计模式的实现),允许我们只实现需要的回调方法。

public abstract class HandlerInterceptorAdapter implements HandlerInterceptor {  
     //省略代码 此处所以三个回调方法都是空实现,preHandle返回true。  
} 
  • 1
  • 2
  • 3

使用方式如下:

//省略import  
public class HandlerInterceptor1 extends HandlerInterceptorAdapter {
//此处继承HandlerInterceptorAdapter适配器即可  
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
        System.out.println("===========HandlerInterceptor1 preHandle");  
        return true;  
    }  
    @Override  
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {  
        System.out.println("===========HandlerInterceptor1 postHandle");  
    }  
    @Override  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {  
        System.out.println("===========HandlerInterceptor1 afterCompletion");  
    }  
}  

拦截器配置中,目录下/**则是所有目录拦截,包括子目录,  若是/*则代表是当前目录下所有目录不包括子目录

 实例:

在做用户权限管理拦截时,需要注意的点:

1)返回的response需要重写 需要重新设置编码 写权限 reset()方法等

2)需要解决用户循环登录问题,即若判断无权限操作要求登录时,登录页面应不予拦截,可在配置中设置exclude

3)注意前端组件返回值的格式问题


  


@Slf4j
public class AuthorityInterceptor implements HandlerInterceptor{

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("preHandle");
        //请求中Controller中的方法名
        HandlerMethod handlerMethod = (HandlerMethod)handler;

        //解析HandlerMethod

        String methodName = handlerMethod.getMethod().getName();
        String className = handlerMethod.getBean().getClass().getSimpleName();

        //解析参数,具体的参数key以及value是什么,我们打印日志
        StringBuffer requestParamBuffer = new StringBuffer();
        Map paramMap = request.getParameterMap();
        Iterator it = paramMap.entrySet().iterator();
        while (it.hasNext()){
            Map.Entry entry = (Map.Entry)it.next();
            String mapKey = (String)entry.getKey();

            String mapValue = StringUtils.EMPTY;

            //request这个参数的map,里面的value返回的是一个String[]
            Object obj = entry.getValue();
            if(obj instanceof String[]){
                String[] strs = (String[])obj;
                mapValue = Arrays.toString(strs);
            }
            requestParamBuffer.append(mapKey).append("=").append(mapValue);
        }

        if(StringUtils.equals(className,"UserManageController") && StringUtils.equals(methodName,"login")){
            log.info("权限拦截器拦截到请求,className:{},methodName:{}",className,methodName);
            //如果是拦截到登录请求,不打印参数,因为参数里面有密码,全部会打印到日志中,防止日志泄露
            return true;
        }

        log.info("权限拦截器拦截到请求,className:{},methodName:{},param:{}",className,methodName,requestParamBuffer.toString());


        User user = null;

        String loginToken = CookieUtil.readLoginToken(request);
        if(StringUtils.isNotEmpty(loginToken)){
            String userJsonStr = RedisShardedPoolUtil.get(loginToken);
            user = JsonUtil.string2Obj(userJsonStr,User.class);
        }

        if(user == null || (user.getRole().intValue() != Const.Role.ROLE_ADMIN)){
            //返回false.即不会调用controller里的方法
            response.reset();//geelynote 这里要添加reset,否则报异常 getWriter() has already been called for this response.
            response.setCharacterEncoding("UTF-8");//geelynote 这里要设置编码,否则会乱码
            response.setContentType("application/json;charset=UTF-8");//geelynote 这里要设置返回值的类型,因为全部是json接口。

            PrintWriter out = response.getWriter();

            //上传由于富文本的控件要求,要特殊处理返回值,这里面区分是否登录以及是否有权限
            if(user == null){
                if(StringUtils.equals(className,"ProductManageController") && StringUtils.equals(methodName,"richtextImgUpload")){
                    Map resultMap = Maps.newHashMap();
                    resultMap.put("success",false);
                    resultMap.put("msg","请登录管理员");
                    out.print(JsonUtil.obj2String(resultMap));
                }else{
                    out.print(JsonUtil.obj2String(ServerResponse.createByErrorMessage("拦截器拦截,用户未登录")));
                }
            }else{
                if(StringUtils.equals(className,"ProductManageController") && StringUtils.equals(methodName,"richtextImgUpload")){
                    Map resultMap = Maps.newHashMap();
                    resultMap.put("success",false);
                    resultMap.put("msg","无权限操作");
                    out.print(JsonUtil.obj2String(resultMap));
                }else{
                    out.print(JsonUtil.obj2String(ServerResponse.createByErrorMessage("拦截器拦截,用户无权限操作")));
                }
            }
            out.flush();
            out.close();//这里要关闭

            return false;

        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion");
    }
}


alt/回车 解决问题

过滤器和拦截器的区别:

  ①拦截器是基于java的反射机制的,而过滤器是基于函数回调。

  ②拦截器依赖于spring容器,过滤器依赖于servlet容器。过滤器只在servlet前后起作用,而拦截器能深入到

方法前后,异常抛出前后。

  ③拦截器只能对controller请求起作用,而过滤器则可以对几乎所有的请求起作用。
  ④拦截器可以访问controller上下文、值栈里的对象,而过滤器不能访问。
  ⑤在controller的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。

  ⑥拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值