资讯专栏INFORMATION COLUMN

SpringMVC HandlerInterceptor诡异问题排查

Baaaan / 2796人阅读

摘要:拦截器注册配置拦截器注册配置通过可以发现中的的清理工作没有得到执行。只会在出现异常,返回或正常执行结束才会从索引依次往前执行。

发现问题

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

代码走读

用户登录下上文

/**
 * 用户登录下上文
 *
 * @author : jamesfu
 * @date : 22/5/2019
 * @time : 9:18 AM
 */
@Data
public class UserContext {
    private final static ThreadLocal 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清理用户登录上下文。

拦截器注册配置

/**
 * 拦截器注册配置
 *
 * @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");
    }
}

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

springmvc 源码走读

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

源码走读:DispatcherServlet

/**
	 * Process the actual dispatching to the handler.
	 * 

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. *

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是不会执行的。所以一些必要的清理工作得不到执行,会出现类似我们遇到的帐号串的问题。

参考资料

Spring 拦截器——HandlerInterceptor

SpringMVC HandlerInterceptor诡异问题排查

关注公众号交流学习

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/6896.html

相关文章

  • SpringMVC【校验器、统一处理异常、RESTful、拦截器】

    摘要:只要有一个拦截器不放行,不能执行完成号不放行和号不放行测试结果总结只有前边的拦截器方法放行,下边的拦截器的才执行。至于他们的拦截器链的调用顺序,和的是没有差别的。 前言 本博文主要讲解的知识点如下: 校验器 统一处理异常 RESTful 拦截器 Validation 在我们的Struts2中,我们是继承ActionSupport来实现校验的...它有两种方式来实现校验的功能 手写...

    marser 评论0 收藏0
  • SpringMVC入门笔记

    摘要:简介注解用于修饰的方法,根据的的内容,通过适当的转换为客户端需要格式的数据并且写入到的数据区,从而不通过视图解析器直接将数据响应给客户端。并且这些解析器都实现了接口,在接口中有四个最为主要的接口方法。 SpringMVC 细节方面的东西很多,所以在这里做一篇简单的 SpringMVC 的笔记记录,方便以后查看。 Spring MVC是当前最优秀的MVC框架,自从Spring 2.5版本...

    gekylin 评论0 收藏0
  • Spring Boot实践——三种拦截器的创建

    摘要:中的拦截器在开发中,拦截器是经常用到的功能。该拦截器只能过滤请求,允许多个拦截器同时存在,通过拦截器链管理。当时不再执行后续的拦截器链及被拦截的请求。实现拦截器大致也分为两种,一种是实现接口,另一种利用的注解或配置。 Spring中的拦截器   在web开发中,拦截器是经常用到的功能。它可以帮我们验证是否登陆、权限认证、数据校验、预先设置数据以及统计方法的执行效率等等。今天就来详细的谈...

    fnngj 评论0 收藏0
  • SpringMVC之源码分析--HandlerMapping(五)

    摘要:概述通过前三章的分析,我们简要分析了和,但对拦截器部分做详细的分析,拦截器的加载和初始化是三个相同的部分。 概述 通过前三章的分析,我们简要分析了SimpleUrlHandlerMapping、BeanNameUrlHandlerMapping和RequestMappingHandlerMapping,但对拦截器部分做详细的分析,拦截器的加载和初始化是三个HandlerMapping相...

    nanchen2251 评论0 收藏0
  • springboot学习(二)——springmvc配置使用

    摘要:中添加拦截器配置如下拦截所有请求,也就是,只拦截开头的请求。在中并没有提供配置文件的方式来配置拦截器,因此需要使用的代码式配置,配置如下这个属性通常并不需要手动配置,高版本的会自动检测第四点讲下静态资源映射。 以下内容,如有问题,烦请指出,谢谢 上一篇讲解了springboot的helloworld部分,这一篇开始讲解如何使用springboot进行实际的应用开发,基本上寻着sprin...

    hiyayiji 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<