SpringMVC HandlerInterceptor诡异问题排查

发现问题

最近在进行压测发现,有一些接口时好时坏,经过sentry日志平台及sky walking平台跟踪发现,用户张三获取到的用户上下文确是李四。spring

代码走读

用户登陆下上文json

/**
 * 用户登陆下上文
 *
 * @author : jamesfu
 * @date : 22/5/2019
 * @time : 9:18 AM
 */
@Data
public class UserContext {
    private final static ThreadLocal<UserContext> threadLocal = new ThreadLocal<>();

    private Long id;

    private String loginName;

    public static UserContext get() {
        UserContext context = threadLocal.get();
        if (context == null) {
            // TODO(james.h.fu):根据请求上下文获取token, 而后恢复用户登陆下上文
            context = new UserContext() {{
                setId(1L);
                setLoginName("james.h.fu1");
            }};
            threadLocal.set(context);
        }

        return context;
    }

    public static void clear() {
        threadLocal.remove();
    }

    public static void set(UserContext context) {
        if (context != null) {
            threadLocal.set(context);
        }
    }
}
复制代码

在拦截器中有调用UserContext.set恢复用户登陆上下文,并在请求结束时调用UserContext.clear清理用户登陆上下文。 tomcat

拦截器在preHandle中根据请求上下文恢复登陆信息

拦截器注册配置bash

/**
 * 拦截器注册配置
 *
 * @author : jamesfu
 * @date : 22/5/2019
 * @time : 9:15 AM
 */
@Configuration
public class FilterConfig implements WebMvcConfigurer {
    @Autowired
    private JsonRpcInterceptor jsonRpcInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jsonRpcInterceptor)
                .addPathPatterns("/json.rpc");
    }
}
复制代码

调试过程当中探测springmvc已注册的拦截器有四个

遇到preHandle返回false就中断调用链条,开始执行afterCompletion

调试日志也验证了确实没有执行afterCompletion中的UserContext.clear

经过debug能够发现UserContext中的ThreadLocal的清理工做没有获得执行。致使请求进来时,有可能ThreadLocal已存在了,就不会再根据请求上下文恢复了。架构

springmvc 源码走读

tomcat 在收到http请求后,最终会交由spring mvc的DispatcherServlet处理。 这里能够从doDispatch按图索骥,顺藤摸瓜地往下看起走。mvc

源码走读:DispatcherServlet

/**
	 * Process the actual dispatching to the handler.
	 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order. * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
	 * to find the first that supports the handler class.
	 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers * themselves to decide which methods are acceptable. * @param request current HTTP request * @param response current HTTP response * @throws Exception in case of any kind of processing failure */ protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception 复制代码

请求会获得分发,而后执行各个已注册Handler的preHandle-->postHandle-->afterCompletion。app

源码走读:HandlerExecutionChain

applyPreHandle
/**
	 * Apply preHandle methods of registered interceptors.
	 * @return {@code true} if the execution chain should proceed with the
	 * next interceptor or the handler itself. Else, DispatcherServlet assumes
	 * that this interceptor has already dealt with the response itself.
	 */
	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = 0; i < interceptors.length; i++) {
				HandlerInterceptor interceptor = interceptors[i];
				if (!interceptor.preHandle(request, response, this.handler)) {
					triggerAfterCompletion(request, response, null);
					return false;
				}
				this.interceptorIndex = i;
			}
		}
		return true;
	}
复制代码

当执行到preHandle返回false时,它就会从上一个返回true的handler依次往前执行afterCompletion,它本身的afterCompletion得不到执行。分布式

triggerAfterCompletion
/**
	 * Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
	 * Will just invoke afterCompletion for all interceptors whose preHandle invocation
	 * has successfully completed and returned true.
	 */
	void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
			throws Exception {

		HandlerInterceptor[] interceptors = getInterceptors();
		if (!ObjectUtils.isEmpty(interceptors)) {
			for (int i = this.interceptorIndex; i >= 0; i--) {
				HandlerInterceptor interceptor = interceptors[i];
				try {
					interceptor.afterCompletion(request, response, this.handler, ex);
				}
				catch (Throwable ex2) {
					logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
				}
			}
		}
	}
复制代码

triggerAfterCompletion只会在(1)出现异常,(2)preHandle返回false 或(3)正常执行结束才会从索引interceptorIndex依次往前执行。ide

因此基于以上源码能够得知,在写拦截器时preHandle返回false时,afterCompletion是不会执行的。因此一些必要的清理工做得不到执行,会出现相似咱们遇到的账号串的问题。post

参考资料

关注公众号交流学习

关注Java分布式架构实战, 持续精进

联系我,学习交流
相关文章
相关标签/搜索