zoukankan      html  css  js  c++  java
  • 【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); } } } }
  • 相关阅读:
    为什么不要在Spring的配置里,配置上XSD的版本号
    使用GIT管理UE4代码
    C++ 编程错误记录
    Maven 命令及其他备忘
    Windows API 之 CreateToolhelp32Snapshot
    Windows API 之 ReadProcessMemory
    Windows API 之 OpenProcessToken、GetTokenInformation
    利用未文档化API:RtlAdjustPrivilege 提权实现自动关机
    WindowsAPI 之 CreatePipe、CreateProcess
    错误: error C4996: 'strcpy': This function or variable may be unsafe. Consider using strcpy_s instead. 的处理方法
  • 原文地址:https://www.cnblogs.com/756623607-zhang/p/11259087.html
Copyright © 2011-2022 走看看