Spring Boot實踐——三種攔截器的建立

引用:https://blog.csdn.net/hongxingxiaonan/article/details/48090075ios

Spring中的攔截器

  在web開發中,攔截器是常常用到的功能。它能夠幫咱們驗證是否登錄、權限認證、數據校驗、預先設置數據以及統計方法的執行效率等等。今天就來詳細的談一下spring中的攔截器。spring中攔截器主要分種,一個是HandlerInterceptor,一個是MethodInterceptor。web

1、HandlerInterceptor攔截器

  HandlerInterceptor是springMVC項目中的攔截器,它攔截的目標是請求的地址,比MethodInterceptor先執行。實現一個HandlerInterceptor攔截器能夠直接實現HandlerInterceptor接口,也能夠繼承HandlerInterceptorAdapter類。這兩種方法異曲同工,其實HandlerInterceptorAdapter也就是聲明瞭HandlerInterceptor接口中全部方法的默認實現,而咱們在繼承他以後只須要重寫必要的方法。下面就是HandlerInterceptorAdapter的代碼,能夠看到一個方法只是默認返回true,另外兩個是空方法:spring

/**
 * 自定義攔截器-基於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作了什麼springboot

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。mvc

這裏能夠根據本身的需求在對應方法中寫本身業務處理邏輯app

/**
 * 自定義攔截器-基於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("/**");//攔截全部請求
    }

}

 

2、MethodInterceptor攔截器

  MethodInterceptor是AOP項目中的攔截器,它攔截的目標是方法,即便不是controller中的方法。實現MethodInterceptor攔截器大體也分爲兩種,一種是實現MethodInterceptor接口,另外一種利用AspectJ的註解或配置。async

一、實現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();
    }

}

配置說明ide

    <bean id="customMethodInterceptor" class="com.onlymate.springboot.interceptor.CustomMethodInterceptor" />
    
    <aop:config proxy-target-class="false">
        <!-- 方法攔截器,基於spring aop 實現配置 -->
        <!-- 掃描使用了註解的方法進行攔截 -->
        <aop:advisor pointcut="@annotation(com.onlymate.springboot.annotation.CustomAnnotation)" advice-ref="customMethodInterceptor" />
        <!-- 指定包路徑下的方法 -->
        <aop:advisor pointcut="execution(* com.onlymate.springboot.controller.*.*(..))" advice-ref="customMethodInterceptor" />
    </aop:config>

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 {};

}

二、利用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、配置說明

    <bean id="customAspectJInterceptor" class="com.onlymate.springboot.interceptor.CustomAspectJInterceptor"/>
    <aop:config proxy-target-class="false">
        <!-- 方法攔截器,基於AspectJ實現方式一 -->
        <aop:aspect ref="customAspectJInterceptor">
            <aop:around method="around" pointcut="execution(* com.onlymate.springboot.controller.*.*(..))"/>
        </aop:aspect>
        
    </aop:config>

    <!-- 方法攔截器,基於AspectJ實現方式二 -->
    <!-- 自動掃描使用了aspectj註解的類 -->
    <aop:aspectj-autoproxy/>

 

3、效果圖

 

4、談一談區別

  上面的兩種攔截器都能起到攔截的效果,可是他們攔截的目標不同,實現的機制不一樣,因此有的時候適用不一樣的場景。HandlerInterceptoer攔截的是請求地址,因此針對請求地址作一些驗證、預處理等操做比較合適。當你須要統計請求的響應時間時MethodInterceptor將不太容易作到,由於它可能跨越不少方法或者只涉及到已經定義好的方法中一部分代碼。MethodInterceptor利用的是AOP的實現機制,在本文中只說明瞭使用方式,關於原理和機制方面介紹的比較少,由於要說清楚這些須要講出AOP的至關一部份內容。在對一些普通的方法上的攔截HandlerInterceptoer就無能爲力了,這時候只能利用AOP的MethodInterceptor。利用MethodInterceptor就能夠很容易的實現一個日誌攔截處理。

        另外,還有一個跟攔截器相似的東西----Filter。Filter是Servlet規範規定的,不屬於spring框架,也是用於請求的攔截。可是它適合更粗粒度的攔截,在請求先後作一些編解碼處理、日誌記錄等。而攔截器則能夠提供更細粒度的,更加靈活的,針對某些請求、某些方法的組合的解決方案。
相關文章
相關標籤/搜索