【Spring Boot】Spring Boot之自定义拦截器

一、拦截器的作用

  将通用的代码抽取出来,达到复用的效果。比如可以用来做日志记录、登录判断、权限校验等等

 

二、如何实现自定义拦截器

1)创建自定义拦截器类并实现HandlerInterceptor类

/**
 * @author zhangboqing
 * @date 2019-07-28
 */
public class MyInterceptor implements HandlerInterceptor {

    /**
     * 执行Controller方法之前,调用
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        
        //返回false,请求将被拦截。放回true代表放行
        return false;
    }

    /**
     * 执行Controller方法之后,响应给前端之前,调用
     */
    @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)将我们自已的拦截器注册到注册器中

/**
 * @author zhangboqing
 * @date 2019-07-28
 * 
 * MVC配置类
 */
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {

    /** 创建自定义拦截器实例 */
    @Bean
    MyInterceptor myInterceptor() {
        return new MyInterceptor();
    }


    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        //注册自定义拦截器
        registry.addInterceptor(myInterceptor())
                //拦截所有请求
                .addPathPatterns("/*")
                //指定需要过滤的请求地址
//                .excludePathPatterns()
        ;
    }
}

 

三、请求日志记录拦截器实现

import com.alibaba.fastjson.JSON;
import com.talkilla.talkillalexile.common.utils.JodaTimeUtils;
import com.talkilla.talkillalexile.common.utils.RandomCodeUtils;
import com.talkilla.talkillalexile.config.filter.HttpHelperUtils;
import com.talkilla.talkillalexile.config.interceptor.model.PostRequestLogInfoModel;
import com.talkilla.talkillalexile.config.interceptor.model.PreRequestLogInfoModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

/**
 * @author zhangboqing
 * @date 2018/10/13
 * <p>
 * 日志打印拦截
 */
@Slf4j
public class LoggingInterceptor implements HandlerInterceptor {


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //生成这次请求的唯一标识
        String requestUUID = RandomCodeUtils.getUUID();
        long logStartTime = System.currentTimeMillis();

        //记录开始时间
        request.setAttribute("logStartTime", logStartTime);
        request.setAttribute("requestUUID",requestUUID);

        //请求日志记录
        preRequestLoggin(request,requestUUID);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        long logEndTime = System.currentTimeMillis();
        long logStartTime = (Long)request.getAttribute("logStartTime");
        String requestUUID = (String)request.getAttribute("requestUUID");

        //记录整个请求的执行时间
        loggingHandleTime(requestUUID,logStartTime,logEndTime);
    }

    private void loggingHandleTime(String requestUUID, long logStartTime, long logEndTime) {
        String logInfo = getLoggingHandleTime(requestUUID,logStartTime,logEndTime);
        log.info("[请求拦截日志信息]:{}", logInfo);
    }

    private String getLoggingHandleTime(String requestUUID, long logStartTime, long logEndTime) {
        PostRequestLogInfoModel build = PostRequestLogInfoModel.builder()
                .requestUUID(requestUUID)
                .requestTime(JodaTimeUtils.timestampToString(logStartTime / 1000, JodaTimeUtils.DateFormat.DATETIME_FORMAT))
                .responseTime(JodaTimeUtils.timestampToString(logEndTime / 1000, JodaTimeUtils.DateFormat.DATETIME_FORMAT))
                .handleTime((logEndTime - logStartTime) + "ms").build();

        return JSON.toJSONString(build);
    }

    /**
     * 请求日志记录
     *
     * @param request
     */
    private void preRequestLoggin(HttpServletRequest request,String requestUUID) {
        //获取相关参数
        //请求地址
        String requestURI = request.getRequestURI();
        //请求方法
        String method = request.getMethod();
        //请求参数
        Map<String, String[]> parameterMap = request.getParameterMap();
        String bodyString = "";
        try {
            bodyString = HttpHelperUtils.getBodyString(request);
        } catch (IOException e) {
            e.printStackTrace();
        }

        String reqestLogInfo = getRequestLogInfo(requestURI, method, parameterMap, bodyString,requestUUID);
        log.info("[请求拦截日志信息]:{}", reqestLogInfo);
    }

    private String getRequestLogInfo(String requestURI, String method, Map<String, String[]> getParameterMap, String postBodyString,String requestUUID) {

        PreRequestLogInfoModel build = PreRequestLogInfoModel.builder()
                .requestUUID(requestUUID)
                .requestURI(requestURI)
                .method(method)
                .getParameter(getParameterMap)
                .postParameter(postBodyString).build();

        return JSON.toJSONString(build);
    }

}
View Code

 

四、从源码角度去理解拦截器三个方法的执行时机

将代码定位到Spring MVC核心处理类DispatcherServlet的doDispatcher()方法,从标记的1,2,3,4,5可以很清楚的看出下面几点启示

  1.启示一:拦截器的preHandle方法是在执行Controller方法之前被调用的

  2.启示二:拦截器的postHandle方法是在执行Controller方法之后被调用的,但是再处理响应结果之前

    3.启示三:拦截器的afterCompletion方法是在处理响应结果之后执行的,也就是说,在调用afterCompletion方法的时候,响应结果已经返回给前端了,该方法的任何处理都不会影响响应结果

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

                // 1.执行拦截器preHandle()方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 2.实际调用处理程序(Controller的方法)
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

                applyDefaultViewName(processedRequest, mv);
          // 3.执行拦截器postHandle()方法 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); }
        // 4.处理响应结果 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { // 5.执行拦截器afterCompletion()方法 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); } } } }

 

posted @ 2019-07-28 15:14  N!CE波  阅读(702)  评论(0编辑  收藏  举报