前言
拦截器是Web中常见,重要的一种机制,用来对数据进行预处理等操作,本文主要是Spring对HandlerInterceptor这个接口进行解析,这个接口属于SpringMVC包中。
分析
三个方法
HandlerInterceptor,这个Handler可以认为就是Controller的方法,这个接口有三个方法,分别是:
其中:
- preHandler是处理前的逻辑,返回true表示应该继续处理,否则表示响应结束;
- postHandler是处理后的逻辑;
- afterCompletion是在渲染完试图后的逻辑,通常用来清理资源的操作,这里注意,如果preHandler里面返回false,那么将触发这个afterCompletion的逻辑,这部分要注意的是,如果返回false,那么将执行的是所有的拦截器的afterCompletion方法,具体逻辑如下所示。
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
它和Servlet Filter的区别
- 处理器拦截器HandlerInterceptor是
Spring MVC
提供的特性,依赖于Spring MVC框架,而不依赖Servlet容器,Filter则是Servlet的特性,属于Servlet
的规范,并且依赖Servlet容器。 - 应用中可以存在多个拦截器形成拦截器链,也可以存在多个过滤器形成过滤器链!
- 拦截器链和过滤器链的预处理和后处理的调用顺序都是相反的,即预处理调用时按照链从前向后调用,而后处理调用时则按照链从后向前调用。
- 过滤器可用于对所有到达该应用的请求进行拦截,而拦截器则只能对通过DispatcherServlet进行处理的请求进行拦截。
- 在Request请求到达Servlet之前执行过滤器的预处理逻辑,在请求到达DispatcherServlet之后、执行Handler之前执行拦截器的预处理逻辑,并在成功执行Handler之后执行拦截器的后处理逻辑,在DispatcherServlet返回之后最后执行过滤器的后处理逻辑。
- Filter在doFilter一个方法中定义预处理和后处理逻辑,在方法中通过
filterChain.doFilter
进行分隔,而HandlerInterceptor将预处理和后处理逻辑拆分成两个方法,即preHandle、postHandle
方法。 - Filter有该类本身的初始化和销毁的回调方法,即
init和destroy
,而HandlerInterceptor则没有,但是HandlerInterceptor拥有afterCompletion
处理方法,无论有没有抛出异常,在DispatcherServlet请求处理的最后都会执行!
实现的原理
通过的是责任链的设计模式,关键角色是HandlerExecutionChain,这个类里面有一个容器,管理了HandlerInterceptor接口。
如何使用
注册Interceptor
@Configuration
public class HeaderParamExtInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry){
// 这个Interceptor实现了HandlerInterceptor接口
registry.addInterceptor(new HeaderParamExtInterceptor()).order(-1);
// 还可以制定拦截的路径
registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
registry.addInterceptor(new HeaderParamInterceptor()).addPathPatterns(new String[]{"/**"}).excludePathPatterns(new String[]{"*.js,*.gif,*.jpg,*.png,*.css,*.ico"});
}
}
应用场景
- 日志记录:通过HandlerInterceptor拦截器记录处理日志,以便后续进行系统监控、问题定位等。
- 流量记录:通过HandlerInterceptor拦截器记录系统流量,进行UV、PV统计,同时可以进行业务限流。
- 业务鉴权:通过HandlerInterceptor对按照响应需求统一进行校验,校验通过与否进行响应处理。
- 修改请求头,将自定义的请求头放在ThreadLocal中进行管理;
示例代码,修改请求头
public class HeaderParamExtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
Map<String, String> headerParamsMap = new HashMap<>(16);
ServletRequestAttributes servletReqAttr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (servletReqAttr != null) {
// 设置账户Id
headerParamsMap.put(CommonConstants.HTTP_HEADER_X_ACCOUNT_ID, request.getHeader(CommonConstants.HTTP_HEADER_X_ACCOUNT_ID));
}
HeaderParamThreadLocalUtils.setHeaderParam(headerParamsMap);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
HeaderParamThreadLocalUtils.removeHeaderParam();
}
/**
* 修改请求头信息
*/
private void modifyHeaders(Map<String, String> headersTmp, HttpServletRequest request) {
if (headersTmp == null || headersTmp.isEmpty()) {
return;
}
Class<? extends HttpServletRequest> requestClass = request.getClass();
try {
Field request1 = requestClass.getDeclaredField("request");
request1.setAccessible(true);
Object o = request1.get(request);
Field coyoteRequest = o.getClass().getDeclaredField("coyoteRequest");
coyoteRequest.setAccessible(true);
Object o1 = coyoteRequest.get(o);
Field headers = o1.getClass().getDeclaredField("headers");
headers.setAccessible(true);
MimeHeaders o2 = (MimeHeaders)headers.get(o1);
for (Map.Entry<String, String> entry : headersTmp.entrySet()) {
o2.removeHeader(entry.getKey());
o2.addValue(entry.getKey()).setString(entry.getValue());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class HeaderParamThreadLocalUtils {
private static ThreadLocal<Map<String, String>> headerParamThreadLocal = ThreadLocal.withInitial(HashMap::new);
public static void setHeaderParam(Map<String, String> headerParam){
if (!CollectionUtils.isEmpty(headerParam)){
headerParamThreadLocal.set(headerParam);
}
}
/**
* 取当前线程对应http请求的请求头
*/
public static Map<String, String> getHeaderParam(){
return headerParamThreadLocal.get();
}
public static void removeHeaderParam(){
headerParamThreadLocal.remove();
}
}
druid中的代码
下面这段代码判断uri后缀是否包某个字符串,取之前的字符串进行处理
if (uriStat == null) {
int index = requestURI.indexOf(";jsessionid=");
if (index != -1) {
requestURI = requestURI.substring(0, index);
uriStat = webAppStat.getURIStat(requestURI, false);
}
}
// 这个方法可以获取正在处理的URI
public String getRequestURI(HttpServletRequest request) {
return (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
}