开发自己的Interceptor

拦截器代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20

@Slf4j
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("preHandle Running");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle Running");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion Running");
    }
}

配置拦截器的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/", "/login", "/css/**");
    }
}

遇到的问题:

  1. WebConfig类上忘记@Configuration,导致配置的拦截器不生效。

拦截器原理

  1. 根据当前请求,找到HandlerExecutionChain(可以处理请求的handler以及handler的所有拦截器)

2021-08-02-09-38-51

  1. 先来顺序执行 所有拦截器的 preHandle方法

    • 如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
    • 如果当前拦截器返回为false。直接倒序执行所有已经执行了的拦截器的afterCompletion
    • 如果任何一个拦截器返回false。直接跳出不执行目标方法
  2. 所有拦截器都返回true。执行目标方法

  3. 倒序执行所有拦截器的postHandle方法。

  4. 前面的步骤有任何异常都会直接倒序触发afterCompletion

  5. 页面成功渲染完成以后,也会倒序触发afterCompletion

2021-07-28-20-30-22

拦截器的应用

UserInfoInterceptor

这个拦截器是用于拦截token信息的,我现在逐行研究该拦截器的实现:

1
2
3
4
5

if (response.getStatus() == HttpServletResponse.SC_NOT_FOUND) {
   return false;
}

这行代码是想处理404错误,目前的实现方案会导致调用接口时如果接口不存在,则返回空请求体。我建议是实现ErrorController解决该问题。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

// 如果不是映射到方法直接通过
if (!(object instanceof HandlerMethod)) {
    return true;
}

if (request.getRequestURI().contains("/internal/")) {
    return true;
}


我不知道这两行代码的含义,我不确定在什么情况下不是映射到方法。而且,在我看来,我们应该在拦截器处时,排除掉静态资源的URL。同样的对于/internal资源,我们可以在配置拦截器时排除掉。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

//检查是否有passtoken注释,有则跳过认证
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
if (method.isAnnotationPresent(PassToken.class)) {
    PassToken passToken = method.getAnnotation(PassToken.class);
    if (passToken.required()) {
        return true;
    }
}

这一段代码是说,如果我们的方法被@PassToken注解了,则直接放行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

String userJson = request.getHeader("user");
JSONObject user;

if (StringUtils.isEmpty(userJson)) {
    if (StringUtils.isEmpty(request.getHeader(APICons.TOKEN))) {
        throw new TokenNotFoundException("token required");
    } else {
        user = apiUtils.currentUser();
        if (user == null) {
            throw new PermissionException("not authorized");
        }

    }
} else {
    try {
        userJson = URLDecoder.decode(userJson, "utf-8");
        user = JSONObject.parseObject(userJson);
    } catch (UnsupportedEncodingException e) {
        throw new ValidateException("user info not validate");
    }
}

request.setAttribute(APICons.REQUEST_USER, user);
request.setAttribute(APICons.REQUEST_USER_ID, user.getString("id"));
request.setAttribute(APICons.REQUEST_COMPANY_ID, user.getString("companyId"));

接下来,我们尝试从Header中获取User信息,如果获取的用户信息为空,我们尝试获取Token信息,如果Token信息也为空,则我们抛出异常。如果Token信息不为框,则我们用Token从Redis中获取用户的信息;如果获取的用户信息不为空,则我们直接解析Token的信息。

最后将解析的信息塞到Attributes中,完美收工。

最后,对该拦截器的配置如下,配置的项过于粗糙,导致我们需要在拦截器中进行假设。

1
2
3

registry.addInterceptor(userInfoInterceptor).addPathPatterns("/**").order(3);

RequestInfoForwardInterceptor

该拦截器是为了让我们的所有返回体带上我们的trace code,核心代码如下:

1
2
3

response.setHeader(APICons.TRACKING_CODE, request.getHeader(APICons.TRACKING_CODE));