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