SpringMVC攔截器的使用場景

笑看風華發表於2018-06-04

微信公眾號:javaFramework
歡迎關注

1.SpringMVC攔截器

1.1 攔截器簡介

Spring web MVC的處理器攔截器類似於Servlet開發中的過濾器Filter,用於對處理器 進行預處理和後處理。

1.2 常見應用場景

1、日誌記錄 :記錄請求資訊的日誌
2、許可權檢查,如登入檢查
3、效能檢測:檢測方法的執行時間

1.2.1 日誌記錄
package com.yaspeed.web.interceptor;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

/**
 * 日誌攔截器 <br>
 * 記錄資訊:訪問時間-Controller路徑-對應方法名-請求引數資訊-請求相對路徑-請求處理時長
 * 
 * @author Administrator
 *
 */
public class LogInterceptor implements HandlerInterceptor {
    public static final Logger LOGGER = LoggerFactory.getLogger(LogInterceptor.class);
    private static final ThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("ThreadLocal StartTime");

    private String getParamString(Map<String, String[]> map) {
        StringBuilder sb = new StringBuilder();
        for (Entry<String, String[]> e : map.entrySet()) {
            sb.append(e.getKey()).append("=");
            String[] value = e.getValue();
            if (value != null && value.length == 1) {
                sb.append(value[0]).append("\t");
            } else {
                sb.append(Arrays.toString(value)).append("\t");
            }
        }
        return sb.toString();
    }

    /**
     * 將ErrorStack轉化為String.
     */
    public static String getStackTraceAsString(Throwable e) {
        if (e == null) {
            return "";
        }
        StringWriter stringWriter = new StringWriter();
        e.printStackTrace(new PrintWriter(stringWriter));
        return stringWriter.toString();
    }

    @Override
    /**
     * 該方法將在請求處理之前進行呼叫<br>
     * 多個Interceptor,然後在SpringMVC會根據宣告的前後順序一個接一個的執行,而且所有的Interceptor中的preHandle方法都會在<br>
     * COntroller方法之前呼叫。SpringMVC的這種Interceptor鏈式結構也是可以中斷的,這種中斷方式時令preHandler的返回值為false<br>
     * 當prehandler的返回值為false的時候整個請求就結束了。
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);
        startTimeThreadLocal.set(startTime); // 執行緒繫結變數(該資料只有當前請求的執行緒可見)
        if (HandlerMethod.class.equals(handler.getClass())) {
            StringBuilder sb = new StringBuilder(1000);
            sb.append("-----------------------開始計時:").append(new SimpleDateFormat("hh:mm:ss.SSS").format(startTime))
                    .append("-------------------------------------\n");
            HandlerMethod h = (HandlerMethod) handler;
            sb.append("Controller: ").append(h.getBean().getClass().getName()).append("\n");
            sb.append("Method    : ").append(h.getMethod().getName()).append("\n");
            sb.append("Params    : ").append(getParamString(request.getParameterMap())).append("\n");
            sb.append("URI       : ").append(request.getRequestURI()).append("\n");
            LOGGER.debug(sb.toString());
        }
        return true;
    }

    /**
     * 在當前請求進行處理之後,也就是Controller 方法呼叫之後執行,但是它會在DispatcherServlet
     * 進行檢視返回渲染之前被呼叫,所以我們可以在這個方法中對Controller 處理之後的ModelAndView 物件進行操作。
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        long startTime = (Long) request.getAttribute("startTime");
        long endTime = System.currentTimeMillis();
        long executeTime = endTime - startTime;
        if (HandlerMethod.class.equals(handler.getClass())) {
            StringBuilder sb = new StringBuilder(1000);
            sb.append("CostTime  : ").append(executeTime).append("ms").append("\n");
            sb.append("-------------------------------------------------------------------------------");
            LOGGER.debug(sb.toString());
        }
    }

    /**
     * 該方法將在整個請求結束之後,也就是在DispatcherServlet 渲染了對應的檢視之後執行。這個方法的主要作用是用於進行資源清理工作的。
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        // 列印JVM資訊。
        if (LOGGER.isDebugEnabled()) {
            long beginTime = startTimeThreadLocal.get();// 得到執行緒繫結的區域性變數(開始時間)
            long endTime = System.currentTimeMillis(); // 2、結束時間

            // 如果controller報錯,則記錄異常錯誤
            if (ex != null) {
                LOGGER.debug("Controller異常: " + getStackTraceAsString(ex));
            }

            LOGGER.debug("計時結束:" + new SimpleDateFormat("hh:mm:ss.SSS").format(endTime) + " 耗時:" + (endTime - beginTime)
                    + " URI:" + request.getRequestURI());
            startTimeThreadLocal.remove();
        }
    }

}

springmvc.xml 中的配置

        <!-- 配置攔截器 -->  
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="com.yaspeed.web.interceptor.LogInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>
1.2.2 許可權檢查
/**
 * 許可權檢查
 * 
 * @author Administrator
 *
 */
public class PermissionInterceptor implements HandlerInterceptor {

    // 在執行handler之前來執行的
    // 用於使用者認證校驗、使用者許可權校驗
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        // 得到請求的url
        String url = request.getRequestURI();

        // 判斷是否是公開 地址
        // 實際開發中需要公開 地址配置在配置檔案中
        // 從配置中取逆名訪問url
        List<String> open_urls = ResourcesUtil.gekeyList("anonymousURL");
        // 遍歷公開 地址,如果是公開 地址則放行
        for (String open_url : open_urls) {
            if (url.indexOf(open_url) >= 0) {
                // 如果是公開 地址則放行
                return true;
            }
        }

        // 從配置檔案中獲取公共訪問地址
        List<String> common_urls = ResourcesUtil.gekeyList("commonURL");
        // 遍歷公用 地址,如果是公用 地址則放行
        for (String common_url : common_urls) {
            if (url.indexOf(common_url) >= 0) {
                // 如果是公開 地址則放行
                return true;
            }
        }

        // 獲取session
        HttpSession session = request.getSession();
        ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser");
        // 從session中取許可權範圍的url
        List<SysPermission> permissions = activeUser.getPermissions();
        for (SysPermission sysPermission : permissions) {
            // 許可權的url
            String permission_url = sysPermission.getUrl();
            if (url.indexOf(permission_url) >= 0) {
                // 如果是許可權的url 地址則放行
                return true;
            }
        }
        // 執行到這裡攔截,跳轉到無權訪問的提示頁面
        request.getRequestDispatcher("/WEB-INF/jsp/refuse.jsp").forward(request, response);
        // 如果返回false表示攔截不繼續執行handler,如果返回true表示放行
        return false;
    }

    // 在執行handler返回modelAndView之前來執行
    // 如果需要向頁面提供一些公用 的資料或配置一些檢視資訊,使用此方法實現 從modelAndView入手
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("HandlerInterceptor1...postHandle");

    }

    // 執行handler之後執行此方法
    // 作系統 統一異常處理,進行方法執行效能監控,在preHandle中設定一個時間點,在afterCompletion設定一個時間,兩個時間點的差就是執行時長
    // 實現 系統 統一日誌記錄
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("HandlerInterceptor1...afterCompletion");
    }

}
1.2.3 效能檢測
/**
 * 實現統計應用效能 攔截器 的實現是單例的,因此不管使用者請求多少次 都 只訪問一個攔截器例項,即執行緒不安全<br>
 * 解決方案:使用ThreadLocal,它是執行緒繫結的變數,提供執行緒區域性變數 (一個執行緒一個ThreadLocal)
 * 
 * @author Administrator
 *
 */
public class TimeInterceptor implements HandlerInterceptor {

    public static final Logger logger = LoggerFactory.getLogger(TimeInterceptor.class);

    // 統計應用效能
    private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<>("StopWatch-StartTime");

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        // 1,開始時間
        long startTime = System.currentTimeMillis();
        // 執行緒繫結變數(該資料只有當前請求的執行緒可見)
        startTimeThreadLocal.set(startTime);

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        // 2.結束時間
        long endTime = System.currentTimeMillis();
        // 得到執行緒繫結的區域性 變數(開始時間)
        long beginTime = startTimeThreadLocal.get();
        // 3.計算消耗時間
        long consumeTime = endTime - beginTime;

        logger.debug("監控==========================: "
                + String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
        startTimeThreadLocal.remove();

    }

}

這裡寫圖片描述

相關文章