资讯专栏INFORMATION COLUMN

Spring Boot实践——三种拦截器的创建

fnngj / 481人阅读

摘要:中的拦截器在开发中,拦截器是经常用到的功能。该拦截器只能过滤请求,允许多个拦截器同时存在,通过拦截器链管理。当时不再执行后续的拦截器链及被拦截的请求。实现拦截器大致也分为两种,一种是实现接口,另一种利用的注解或配置。

Spring中的拦截器

  在web开发中,拦截器是经常用到的功能。它可以帮我们验证是否登陆、权限认证、数据校验、预先设置数据以及统计方法的执行效率等等。今天就来详细的谈一下spring中的拦截器。spring中拦截器主要分种,一个是HandlerInterceptor,一个是MethodInterceptor。

一、HandlerInterceptor拦截器

  HandlerInterceptor是springMVC项目中的拦截器,它拦截的目标是请求的地址,比MethodInterceptor先执行。实现一个HandlerInterceptor拦截器可以直接实现HandlerInterceptor接口,也可以继承HandlerInterceptorAdapter类。这两种方法殊途同归,其实HandlerInterceptorAdapter也就是声明了HandlerInterceptor接口中所有方法的默认实现,而我们在继承他之后只需要重写必要的方法。下面就是HandlerInterceptorAdapter的代码,可以看到一个方法只是默认返回true,另外两个是空方法:

/** * 自定义拦截器-基于springmvc
 * @ClassName: CustomInterceptor 
 * @Description: springMVC项目中的拦截器,它拦截的目标是请求的地址,比MethodInterceptor先执行。
 *                 该拦截器只能过滤action请求,SPring允许多个拦截器同时存在,通过拦截器链管理。
 *                 当preHandle return true时,执行下一个拦截器,直到所有拦截器执行完,再运行被拦截的请求。
 *                 当preHandle return false时, 不再执行后续的拦截器链及被拦截的请求。
 * @author OnlyMate
 * @Date 2018年8月28日 下午2:30:22  
 * */
public class CustomInterceptor implements HandlerInterceptor  {

    @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // TODO Auto-generated method stub
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

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

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

}

这三个方法都是干什么的,有什么作用,什么时候调用,不同的拦截器之间是怎样的调用顺序呢?这还得参考一下DispatcherServlet的doDispatch方法

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // Actually invoke the handler.
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                // As of 4.3, we"re processing Errors thrown from handler methods as well,
                // making them available for @ExceptionHandler methods and other scenarios.
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new NestedServletException("Handler processing failed", err));
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                // Clean up any resources used by a multipart request.
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }

  代码有点长,但是它封装了springMVC处理请求的整个过程。首先根据请求找到对应的HandlerExecutionChain,它包含了处理请求的handler和所有的HandlerInterceptor拦截器;然后在调用hander之前分别调用每个HandlerInterceptor拦截器的preHandle方法,若有一个拦截器返回false,则会调用triggerAfterCompletion方法,并且立即返回不再往下执行;若所有的拦截器全部返回true并且没有出现异常,则调用handler返回ModelAndView对象;再然后分别调用每个拦截器的postHandle方法;最后,即使是之前的步骤抛出了异常,也会执行triggerAfterCompletion方法。关于拦截器的处理到此为止,接下来看看triggerAfterCompletion做了什么

private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
            @Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {

        if (mappedHandler != null) {
            mappedHandler.triggerAfterCompletion(request, response, ex);
        }
        throw ex;
    }

  根据以上的代码,分析一下不同拦截器及其方法的执行顺序。假设有5个拦截器编号分别为12345,若一切正常则方法的执行顺序是12345的preHandle,54321的postHandle,54321的afterCompletion。若编号3的拦截器的preHandle方法返回false或者抛出了异常,接下来会执行的是21的afterCompletion方法。这里要注意的地方是,我们在写一个拦截器的时候要谨慎的处理preHandle中的异常,因为这里一旦有异常抛出就不会再受到这个拦截器的控制。12345的preHandle的方法执行过之后,若handler出现了异常或者某个拦截器的postHandle方法出现了异常,则接下来都会执行54321的afterCompletion方法,因为只要12345的preHandle方法执行完,当前拦截器的拦截器就会记录成编号5的拦截器,而afterCompletion总是从当前的拦截器逆向的向前执行。
  另外,实现HandlerInterceptor拦截器还有一个方法,就是实现WebRequestInterceptor接口。其实它和刚才的两种方法也是殊途同归,最终还是被spring适配成HandlerInterceptor。有一点不同,它的preHandle方法最终只会返回true。

这里可以根据自己的需求在对应方法中写自己业务处理逻辑

/**
 * 自定义拦截器-基于springmvc
 * @ClassName: CustomInterceptor 
 * @Description: springMVC项目中的拦截器,它拦截的目标是请求的地址,比MethodInterceptor先执行。
 *                 该拦截器只能过滤action请求,SPring允许多个拦截器同时存在,通过拦截器链管理。
 *                 当preHandle return true时,执行下一个拦截器,直到所有拦截器执行完,再运行被拦截的请求。
 *                 当preHandle return false时, 不再执行后续的拦截器链及被拦截的请求。
 * @author OnlyMate
 * @Date 2018年8月28日 下午2:30:22  
 *
 */
public class CustomInterceptor implements HandlerInterceptor  {
    private Logger logger = LoggerFactory.getLogger(CustomInterceptor.class);
    
    /**
     * 在请求处理之前执行,主要用于权限验证、参数过滤等
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        logger.info("CustomInterceptor ==> preHandle method: do request before");
        return true;
    }

    /**
     * 当前请求进行处理之后执行,主要用于日志记录、权限检查、性能监控、通用行为等
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        logger.info("CustomInterceptor ==> postHandle method: do request after");
    }

    /**
     * 当前对应的interceptor的perHandle方法的返回值为true时,postHandle执行完成并渲染页面后执行,主要用于资源清理工作
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        logger.info("CustomInterceptor ==> afterCompletion method: do request finshed");
    }
}

配置如下:

/**
 * Web MVC 配置适配器
 * @ClassName: WebAppConfigurer 
 * @Description: 
 * @author OnlyMate
 * @Date 2018年8月28日 下午2:39:31  
 * 
 * WebAppConfigurer extends WebMvcConfigurerAdapter 在Spring Boot2.0版本已过时了,用官网说的新的类替换
 *
 */
@Configuration
public class WebAppConfigurer implements WebMvcConfigurer {
    /**
     * 注入自定义拦截器
     * @Title: addInterceptors 
     * @Description: 先add的拦截器会越靠外,即越靠近浏览器
     * @Date 2018年8月28日 下午2:47:28 
     * @author OnlyMate
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        WebMvcConfigurer.super.addInterceptors(registry);
        registry.addInterceptor(new CustomInterceptor()).addPathPatterns("/**");//拦截所有请求
    }

}
二、MethodInterceptor拦截器

  MethodInterceptor是AOP项目中的拦截器,它拦截的目标是方法,即使不是controller中的方法。实现MethodInterceptor拦截器大致也分为两种,一种是实现MethodInterceptor接口,另一种利用AspectJ的注解或配置。

1、实现MethodInterceptor接口
/**
 * 自定义拦截器-方法拦截器,基于spring aop
 * @ClassName: CustomMethodInterceptor 
 * @Description: AOP项目中的拦截器,它拦截的目标是方法
 *                 配置在applicationContext.xml中
 * @author OnlyMate
 * @Date 2018年8月29日 下午3:35:24  
 *
 */
public class CustomMethodInterceptor implements MethodInterceptor {
    private Logger logger = LoggerFactory.getLogger(CustomMethodInterceptor.class);
    
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        logger.info("CustomMethodInterceptor ==> invoke method: process method name is {}", invocation.getMethod().getName());
        
        //TODO 处理操作
        
        return invocation.proceed();
    }

}

配置说明


    
    
        
        
        
        
        
    

CustomAnnotation自定义注解

/**
 * 自定义注解对象
 * @ClassName: TableSplit 
 * @Description: TODO
 * @author OnlyMate
 * @Date 2018年5月22日 上午11:43:57  
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {
    /** 需拦截方法名描述 */
    String name() default "";
    
    /** 加密 */
    String[] encrypt() default {};

    /** 解密 */
    String[] decrypt() default {};

}
2、利用AspectJ的注解或配置  a、基于AspectJ注解
/**
 * 自定义拦截器-方法拦截器,基于注解的AspectJ方式
 * @ClassName: CustomAutoAspectJInterceptor 
 * @Description: 配置在applicationContext.xml中
 * @author OnlyMate
 * @Date 2018年8月29日 下午4:03:49  
 *
 */
@Component
@Aspect
public class CustomAutoAspectJInterceptor {
    private Logger logger = LoggerFactory.getLogger(CustomAutoAspectJInterceptor.class);
    
    @Around("execution (* com.onlymate.springboot.controller.*.*(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable{
        logger.info("CustomAutoAspectJInterceptor ==> invoke method: process method class is {}", point.getTarget().getClass());
        
        //TODO 处理操作
        
        return point.proceed();
    }
}
b、基于AspectJ配置
/**
 * 自定义拦截器-方法拦截器,基于AspectJ方式
 * @ClassName: CustomAspectJInterceptor 
 * @Description: 配置在applicationContext.xml中
 * @author OnlyMate
 * @Date 2018年8月29日 下午4:03:49  
 *
 */
public class CustomAspectJInterceptor {
    private Logger logger = LoggerFactory.getLogger(CustomAspectJInterceptor.class);
    
    public Object around(ProceedingJoinPoint point) throws Throwable{
        logger.info("CustomAspectJInterceptor ==> invoke method: process method class is {}", point.getTarget().getClass());
        
        //TODO 处理操作
        
        return point.proceed();
    }
}
c、配置说明

    
        
        
            
        
        
    

    
    
    
三、效果图

四、谈一谈区别

  上面的两种拦截器都能起到拦截的效果,但是他们拦截的目标不一样,实现的机制不同,所以有的时候适用不同的场景。HandlerInterceptoer拦截的是请求地址,所以针对请求地址做一些验证、预处理等操作比较合适。当你需要统计请求的响应时间时MethodInterceptor将不太容易做到,因为它可能跨越很多方法或者只涉及到已经定义好的方法中一部分代码。MethodInterceptor利用的是AOP的实现机制,在本文中只说明了使用方式,关于原理和机制方面介绍的比较少,因为要说清楚这些需要讲出AOP的相当一部分内容。在对一些普通的方法上的拦截HandlerInterceptoer就无能为力了,这时候只能利用AOP的MethodInterceptor。利用MethodInterceptor就可以很容易的实现一个日志拦截处理。

   另外,还有一个跟拦截器类似的东西----Filter。Filter是Servlet规范规定的,不属于spring框架,也是用于请求的拦截。但是它适合更粗粒度的拦截,在请求前后做一些编解码处理、日志记录等。而拦截器则可以提供更细粒度的,更加灵活的,针对某些请求、某些方法的组合的解决方案。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76210.html

相关文章

  • springboot整合shiro使用shiro-spring-boot-web-starter

    摘要:此文章仅仅说明在整合时的一些坑并不是教程增加依赖集成依赖配置三个必须的用于授权和登录创建自己的实例用于实现权限三种方式实现定义权限路径第一种使用角色名定义第二种使用权限定义第三种使用接口的自定义配置此处配置之后需要在对应的 此文章仅仅说明在springboot整合shiro时的一些坑,并不是教程 增加依赖 org.apache.shiro shiro-spring-...

    sevi_stuo 评论0 收藏0
  • Spring Boot - 整合JdbcTemplate、MyBatis

    摘要:更简答的说就是要么全部执行成功,要么撤销不执行。因此,数据库操作的事务习惯上就称为事务。实现原理单机事务事务是用对象控制的。接口提供了两种事务模式自动提交和手工提交。事务多机事务,通过实现,需要驱动支持。局限于应用使用。 Spring Boot - 数据库配置 回顾 Spring Boot - 初识 Hello World Spring Boot - Servlet、过滤器、监听器、...

    Keagan 评论0 收藏0
  • 猫头鹰深夜翻译:使用SpringBoot和AspectJ实现AOP

    摘要:我们会写切面来拦截对这些业务类和类的调用。切面定义何时拦截一个方法以及做什么和在一起成为切面连接点当代码开始执行,并且切点的条件满足时,通知被调用。 前言 这篇文章会帮助你使用Spring Boot Starter AOP实现AOP。我们会使用AspectJ实现四个不同的通知(advice),并且新建一个自定义的注解来追踪方法的执行时间。 你将会了解 什么是交叉分割关注点(cross...

    meislzhua 评论0 收藏0
  • Spring Boot 进阶

    摘要:我们可不可以提供一个公共的入口进行统一的异常处理呢当然可以。一般我们可以在地址上带上版本号,也可以在参数上带上版本号,还可以再里带上版本号,这里我们在地址上带上版本号,大致的地址如,其中,即代表的是版本号。 上一篇带领大家初步了解了如何使用 Spring Boot 搭建框架,通过 Spring Boot 和传统的 SpringMVC 架构的对比,我们清晰地发现 Spring Boot ...

    galaxy_robot 评论0 收藏0

发表评论

0条评论

fnngj

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<