开发框架-Spring-Interceptor拦截器

1,282 阅读4分钟

Spring拦截器

描述

顾名思义,起到拦截作用的工具,在日常的开发中,可以使用它去拦截请求,验证一下是否以登录,校验一下权限,或者预先设置一些数据这样的事情;或者在某个方法被调用之前拦截一下,做点什么事。

下面有计算请求花费时间的小例子与源码分析。

在Spring中拦截器主要有两种

  • HandlerInterceptor

    SpringMVC中的拦截器,它的目标是拦截符合条件的请求的

    实现 HandlerIntercepot接口,实现里面的方法就能做到,使用起来非常简单。

  • MethodInterceptor

    这个是针对方法进行拦截

代码

这里使用的是Spring是5.0的版本

HandlerInterceptor

下面实现的拦截器是一个计算请求时间的拦截器。首先写一个拦截器的配置类

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author xigua
 * FrameWork5比4有一定的改变,在5中直接使用实现接口的方法,在4中需要继承WebMvcConfigurerAdapter 去实现,
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //多个拦截器组成一个拦截链
        //addPathPatterns   用于添加拦截规则
        //addInterceptor    用于添加拦截器到拦截链中
        //只有走DispatcherServlet的请求才会走拦截器链
		//这里 /* 表示拦截所有请求
        registry.addInterceptor(interceptorCalculateRequestTime()).addPathPatterns("/*");
    }

    @Bean
    public InterceptorCalculateRequestTime interceptorCalculateRequestTime() {
        return new InterceptorCalculateRequestTime();
    }

}

接下来编写具体的拦截内容

import com.sun.management.OperatingSystemMXBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.management.ManagementFactory;
import java.sql.Timestamp;

/**
 * @author xigua
 * 自定义拦截器,实现HandlerInterceptor方法
 * 计算请求花费时间
 */
public class InterceptorCalculateRequestTime implements HandlerInterceptor {
    private static final Logger LOGGER = LoggerFactory.getLogger(HandlerInterceptor.class);


    /**
     * 只要符合条件的请求就会进入到这个方法,返回true表示通过检验,允许继续运行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        setParameter(request);
        printMemory(request);
        return true;
    }


    /**
     * @param request
     * @param response
     * @param handler
     * @param modelAndView 如果执行中报错不会走这个方法,走此方法的条件是成功执行完调用。
     * 这里也计算了一下时间,可以看出渲染需要多长时间
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        computeConsumeTime(request, "处理完成时");
        printMemory(request);
    }

    /**
     * @param request
     * @param response
     * @param handler
     * @param ex       不论是否报错,最后都会走这个方法,时间以这里的为准,在前台页面渲染完之后执行
     一般来说这个方法用来清理一些资源占用的
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        computeConsumeTime(request, "请求结束时");
        printMemory(request);

    }

    private void computeConsumeTime(HttpServletRequest request, String processName) {
        String requestId = (String) request.getAttribute("requestId");
        long startTime = (long) request.getAttribute("requestStartTime");
        System.out.println("请求Id\t" + requestId + "\t" + processName + "共花费" + (System.currentTimeMillis() - startTime) / 1000 + "秒");
    }

    private void printMemory(HttpServletRequest request) {
        long vmFree;
        long vmUse;
        long vmTotal;
        long vmMax;
        int byteToMb = 1024 * 1024;


        //输出虚拟机内存情况
        Runtime runtime = Runtime.getRuntime();
        vmTotal = runtime.totalMemory() / byteToMb;
        vmFree = runtime.freeMemory() / byteToMb;
        vmMax = runtime.maxMemory() / byteToMb;
        vmUse = vmTotal - vmFree;
        // 操作系统级内存情况查询
        OperatingSystemMXBean operatingSystemMXBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
        long physicalFree = operatingSystemMXBean.getFreePhysicalMemorySize() / byteToMb;
        long physicalTotal = operatingSystemMXBean.getTotalPhysicalMemorySize() / byteToMb;
        long physicalUse = physicalTotal - physicalFree;

        System.out.println("时间" + new Timestamp(System.currentTimeMillis())
                + "请求Id:" + request.getAttribute("requestId") + "\t"
                + "请求地址:" + request.getRequestURI() + "\t"
                + "JVM已用空间" + vmUse + "MB\t"
                + "JVM空闲空间" + vmFree + "MB\t"
                + "JVM总空间" + vmTotal + "MB\t"
                + "JVM可用最大空间" + vmMax + "MB\t"
                + "操作系统物理内存已用的空间为:" + physicalFree + " MB\t"
                + "操作系统物理内存的空闲空间为:" + physicalUse + " MB\t"
                + "操作系统总物理内存:" + physicalTotal + " MB\t");

    }

    /**
     * @param request 设置请求参数
     */
    private void setParameter(HttpServletRequest request) {
        //规则:请求地址+时间戳确定唯一
        long Sc = System.currentTimeMillis();
        String requestId = request.getRequestURL() + String.valueOf(Sc);
        //设置一个请求ID、一个开始时间戳
        request.setAttribute("requestStartTime", Sc);
        request.setAttribute("requestId", requestId);
    }

}

MethodInterceptor【待补充】


源码分析

HandlerInterceptor

这里的重点主要就是实现接口中的方法之后,他们的调用顺序。

这里主要参考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);

				//  确定当前请求的处理程序,包括拦截器还有Controller的方法
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// 确定处理程序的适配器,一般情况下为RequestMappingHandlerAdapter
				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;
					}
				}

                //重点方法,请求预处理,内部为一个拦截器的数组循环调用,调用preHandle方法
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// 通过预处理之后,实际调用处理程序,也可以理解为调用controller
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

				applyDefaultViewName(processedRequest, mv);
                //调用拦截器中的postHandler方法
				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);
			}
            //调用完成之后处理调用结果,里面也有triggerAfterCompletion这个方法,这个方法用来调用拦截器当中afterCompletion的方法
			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);
				}
			}
		}
	}