SpringMVC,拦截器接口HandlerInterceptor

前言

拦截器是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"});


    }

}

应用场景

  1. 日志记录:通过HandlerInterceptor拦截器记录处理日志,以便后续进行系统监控、问题定位等。
  2. 流量记录:通过HandlerInterceptor拦截器记录系统流量,进行UV、PV统计,同时可以进行业务限流。
  3. 业务鉴权:通过HandlerInterceptor对按照响应需求统一进行校验,校验通过与否进行响应处理。
  4. 修改请求头,将自定义的请求头放在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);
    }


 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值