Spring MVC组件开发-拦截器

拦截器


        拦截器是spring MVC中强大的控件,它可以在进入处理器之前做一些操作,或者在处理器完成后进行操作,甚至是在渲染视图后进行操作。spring mvc会在启动期间就通过@RequestMapping的注解解析URI和处理器的对应关系,在运行的时候通过请求找到对应的HandlerMapping,然后构建HandlerExecutionChain对象,它是一个执行的责任链对象。

        对于拦截器所需要关注的有两点,一个是它有哪些方法,方法的含义是什么;第二个是它各个方法在流程中执行的顺序是如何。

拦截器的定义


        spring要求处理器的拦截器都要实现org.springframework.web.servlet.HandlerInterceptor接口,这个接口定义了三个方法:

public interface HandlerInterceptor {  
  
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)  
            throws Exception {  
  
        return true;  
    }  
  
      
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,  
            @Nullable ModelAndView modelAndView) throws Exception {  
    }  
  
   
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,  
            @Nullable Exception ex) throws Exception {  
    }  
  
}  

这三个方法的意义:

  • preHandler方法:在处理器之前执行的前置方法,这样spring mvc可以在进入处理器前处理一些方法了。它返回一个boolean值,会影响到后面的流程。
  • postHandler方法:在处理器之后执行的后置方法,处理器的逻辑完成后运行它。
  • afterCompletion方法:无论是否产生异常都会在渲染视图后执行的方法。

       在新版本中,这三个方法被定义成了默认方法,因为有时候我们可能只需要实现三个回调方法中的某一个,在老版本中要想需要的回调方法拦截器的执行流程的话。spring提供了一个HandlerInterceptorAdapter适配器(一种适配器设计模式的实现)。

拦截器的执行流程


       拦截器可能有多个,后面再来看多个拦截器的执行流程,这里先看一个拦截器的流程:

       在进入处理器之前或者之后处理一些逻辑,或者是在渲染视图后处理一些逻辑,都是允许的。有时需要自己实现一些拦截器,以加强请求的功能。注意:当前置方法返回false时,就不会再执行后面的逻辑了。在拦截器中可以完成前置方法、后置方法和完成方法的相关逻辑。

开发拦截器


      开发一个角色拦截器,它只是一个简单的测试,我们可以实现HandlerInterceptor接口,也可以继承HandlerInterceptorAdapter方法,因为在我们使用的版本中,HandlerInterceptor里的方法被定义为默认方法,根据需要覆盖掉特定方法即可。

public class RoleInterceptor implements HandlerInterceptor {  
    /** 
     * 拦截器处理器处理之前会先经过该方法:前置方法 
     * @return 如果返回true,会进入(放行)下一个拦截器(链) 
     */  
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
        System.out.println("前置方法,返回true,进入后面的处理流程,返回false,完成处理,请求结束");  
        return true;  
    }  
  
    @Override  
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {  
        System.out.println("后置方法,处理器完成处理,视图渲染之前调用");  
    }  
  
    @Override  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {  
        System.out.println("完成方法:视图渲染完成");  
    }  
}  

配置拦截器


<mvc:interceptors>  
        <mvc:interceptor>  
            <mvc:mapping path="/controll/**"/>  
            <bean class="com.wise.bbs.interceptor.RoleInterceptor"/>  
        </mvc:interceptor>  
 </mvc:interceptors> 

        在xml配置中,用元素<mvc:interceptors>配置拦截器,在里面可以配置多个拦截器,path属性告诉拦截器拦截什么请求,它使用一个正则式的匹配。下面介绍下用java代码 配置:

多个拦截器的执行顺序


      多个拦截器会以怎样的顺序执行呢,首先讨论preHandler方法返回为true的情况,先建三个角色拦截器

public class RoleInterceptor1 implements HandlerInterceptor {  
     
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
        System.out.println("preHandle1");  
        return true;  
    }  
  
    @Override  
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {  
        System.out.println("postHandle1");  
    }  
  
    @Override  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {  
        System.out.println("afterCompletion1");  
    }  
}  
  
  
public class RoleInterceptor2 implements HandlerInterceptor {  
     
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
        System.out.println("preHandle2");  
        return true;  
    }  
  
    @Override  
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {  
        System.out.println("postHandle2");  
    }  
  
    @Override  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {  
        System.out.println("afterCompletion2");  
    }  
}  
  
  
public class RoleInterceptor3 implements HandlerInterceptor {  
     
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
        System.out.println("preHandle3");  
        return true;  
    }  
  
    @Override  
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {  
        System.out.println("postHandle3");  
    }  
  
    @Override  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {  
        System.out.println("afterCompletion3");  
    }  
}  

 配置多个拦截器

@Override  
public void addInterceptors(InterceptorRegistry registry) {  
    registry.addInterceptor(new RoleInterceptor1()).addPathPatterns("/control/**");  
    registry.addInterceptor(new RoleInterceptor2()).addPathPatterns("/control/**");  
    registry.addInterceptor(new RoleInterceptor3()).addPathPatterns("/control/**");  
}  

对其进行测试,可以看到结果是这样的 (责任链模式)

preHandle1
preHandle2
preHandle3
......控制器逻辑日志打印.....
postHadle3
postHadle2
postHadle1
...........
afterCompletion3
afterCompletion2
afterCompletion1

有些时候前置方法可能返回false,我们将RoleInterceptor2中的前置方法给为false进行测试后得到的结果:

preHandle1
preHandle2
afterCompletion1

 注意:当其中一个preHandle方法返回false后,按配置顺序,后面的preHandle方法都不会执行了,而控制器和后面的postHandle也不会再运行。

应用   性能监控[引用自开涛博客内容]


如记录一下请求的处理时间,得到一些慢请求(如处理时间超过500毫秒),从而进行性能改进,一般的反向代理服务器如apache都具有这个功能,但此处我们演示一下使用拦截器怎么实现。

实现分析:

1、在进入处理器之前记录开始时间,即在拦截器的preHandle记录开始时间;

2、在结束请求处理之后记录结束时间,即在拦截器的afterCompletion记录结束实现,并用结束时间-开始时间得到这次请求的处理时间。

问题:

我们的拦截器是单例,因此不管用户请求多少次都只有一个拦截器实现,即线程不安全,那我们应该怎么记录时间呢?

解决方案是使用ThreadLocal,它是线程绑定的变量,提供线程局部变量(一个线程一个ThreadLocal,A线程的ThreadLocal只能看到A线程的ThreadLocal,不能看到B线程的ThreadLocal)。

public class StopWatchHandlerInterceptor implements HandlerInterceptor {    
    //线程局部变量,拦截器是单例形式存在,所以每一个请求的开始时间应该绑定在当前线程上      
    private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("StopWatch-StartTime");    
    @Override    
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,     
Object handler) throws Exception {    
        long beginTime = System.currentTimeMillis();//1、开始时间    
        startTimeThreadLocal.set(beginTime);//线程绑定变量(该数据只有当前请求的线程可见)    
        return true;//继续流程    
    }    
        
    @Override    
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,     
Object handler, Exception ex) throws Exception {    
        long endTime = System.currentTimeMillis();//2、结束时间    
        long beginTime = startTimeThreadLocal.get();//得到线程绑定的局部变量(开始时间)    
        long consumeTime = endTime - beginTime;//3、消耗的时间    
        if(consumeTime > 500) {//此处认为处理时间超过500毫秒的请求为慢请求    
            //TODO 记录到日志文件    
            System.out.println(    
String.format("%s consume %d millis", request.getRequestURI(), consumeTime));    
        }            
    }    
}  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值