SpringMVC HandlerInterceptor诡异问题排查

栏目: Java · 发布时间: 5年前

内容简介:最近在进行压测发现,有一些接口时好时坏,通过sentry日志平台及sky walking平台跟踪发现,用户张三获取到的用户上下文确是李四。用户登录下上文在拦截器中有调用UserContext.set恢复用户登录上下文,并在请求结束时调用UserContext.clear清理用户登录上下文。

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

代码走读

用户登录下上文

/**
 * 用户登录下上文
 *
 * @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清理用户登录上下文。

SpringMVC HandlerInterceptor诡异问题排查

拦截器注册配置

/**
 * 拦截器注册配置
 *
 * @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 HandlerInterceptor诡异问题排查
SpringMVC HandlerInterceptor诡异问题排查
SpringMVC HandlerInterceptor诡异问题排查

通过debug可以发现UserContext中的ThreadLocal的清理工作没有得到执行。导致请求进来时,有可能ThreadLocal已存在了,就不会再根据请求上下文恢复了。

springmvc 源码走读

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

源码走读: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。

源码走读: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依次往前执行。

所以基于以上源码可以得知,在写拦截器时preHandle返回false时,afterCompletion是不会执行的。所以一些必要的清理工作得不到执行,会出现类似我们遇到的帐号串的问题。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

疯狂的独角兽

疯狂的独角兽

丹·莱昂斯 / 王天任 / 海南出版社 / 2017-10 / 42

★商业与文学的有机结合,真实与虚幻间嬉笑怒骂,幽默风趣、引人入胜、发人深省的商业小说。 ★《纽约时报》《华尔街日报》《旧金山纪事报》Amazon畅销书,《财富》《纽约邮报》《新闻周刊》《华盛顿邮报》、畅销书《硅谷钢铁侠》作者阿什利·万斯、畅销书《一网打尽》作者布拉德·斯通联袂推荐。 ★作者丹·莱昂斯集小说家、记者、编剧为一身——HBO经典热门剧、豆瓣高分美剧《硅谷》作者;畅销书《乔布斯......一起来看看 《疯狂的独角兽》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具