1、前言
单体应用时,我们经常会把一些共享数据,比如登录信息等放在session里面,当然也可以放在ThreadLocal里面。随着业务复杂度的提高,分布式应用越来越主流。单机的存储的思想已经不适用了,共享session应运而生,比如nosql、session复制等技术方案。
分布式架构中SpringCloud使用尤为广泛。如果想通过Feign接口的调用隐式的传递一些参数,比如用户ID、名称,接口的验签等,我们应该怎么实现呢。Feign为我们提供全链路Request拦截器:feign.RequestInterceptor
今天通过传递用户名到feign服务端,来使用 RequestInterceptor 来实现, 同时对比一下web的拦截器 HandlerInterceptor 的区别。
2、实现思路
调用端和服务端是两个不同的服务,ThreadLocal的方式不能横跨里两个服务。feign.RequestInterceptor 接口可以将请求转发到Feign接口对应的服务中。
- 创建web拦截器,模拟登录请求,将用户名存放到ThreadLocal中。
- 调用Feign接口之前,会调用 feign.RequestInterceptor接口,将ThreadLocal中信息存放到Feign接口的Request中。
- 通过Feign接口的Request对象就可以获取用户名
3、最佳实践-调用端
3.1 调用端-web拦截器模拟登录
public class IWebRequestInterceptor implements AsyncHandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 模拟登录
Map<String, String> stringStringMap = new HashMap<>();
stringStringMap.put("username", "admin");
RequestHolder.set(stringStringMap);
return AsyncHandlerInterceptor.super.preHandle(request, response, handler);
}
}
3.2 调用端-RequestHolder
public class RequestHolder implements AutoCloseable{
private RequestHolder() {}
private static final ThreadLocal<Map<String, String>> THREAD_LOCAL = new ThreadLocal<Map<String, String>>();
public static void set(Map<String, String> map){
THREAD_LOCAL.set(map);
}
public static Map<String, String> get(){
return THREAD_LOCAL.get() == null ? new HashMap<String, String>() : THREAD_LOCAL.get();
}
public static void remove() {
THREAD_LOCAL.remove();
}
@Override
public void close() throws Exception {
THREAD_LOCAL.remove();
}
}
3.3 调用端-调用Feign之前的feign.RequestInterceptor
@Configuration
public class IRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
Map<String, String> stringStringMap = RequestHolder.get();
if (stringStringMap != null) {
for (Map.Entry<String, String> entry : stringStringMap.entrySet()){
template.header(entry.getKey(), entry.getValue());
}
}
}
}
3.4 调用端-拦截器的配置
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new IWebRequestInterceptor()).addPathPatterns("/**");
WebMvcConfigurer.super.addInterceptors(registry);
}
}
调用端的实现,已经实现了用户名传递到服务端。
4、测试结果
服务端结果获取
结果打印:
4、最佳实践-服务端
服务端本身需要配置就可以从Request中获取Header中的用户名。但是服务端也可能调用其他服务端,资深作为调用端。所以服务端也应该要有和调用端一样的配置,最终把用户名保存再自己的服务的ThreadLocal里面。既可以从ThreadLocal里面去也可以从Request里面取。
配置同调用端。
5、HandlerInterceptor 和 RequestInterceptor 的区别
- HandlerInterceptor是 org.springframework.web.servlet(spring-webmvc)包下,而RequestInterceptor 是feign(feign-core)包下。
- HandlerInterceptor拦截器是需要配置拦截的请求,RequestInterceptor 不用,只需要交给Spring管理即可。
- HandlerInterceptor优先与RequestInterceptor 执行,RequestInterceptor 在调用Feign接口之前执行