当我们再用feign调用的时候,如果对应服务需要token验证则需要我们传递token
网上提供的方法都是添加如下配置:
@Configuration
public class FeignConfig implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes());
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
requestTemplate.header("token", request.getHeader("token"));
}
}
}
但是使用这个的时候由于fegin是使用子线程调用的,导致获取到的attributes
不存在,所以我们要使用可继承的InheritableThreadLocal
来保存
默认如下所示
public class RequestContextListener implements ServletRequestListener {
private static final String REQUEST_ATTRIBUTES_ATTRIBUTE =
RequestContextListener.class.getName() + ".REQUEST_ATTRIBUTES";
@Override
public void requestInitialized(ServletRequestEvent requestEvent) {
if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {
throw new IllegalArgumentException(
"Request is not an HttpServletRequest: " + requestEvent.getServletRequest());
}
HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
ServletRequestAttributes attributes = new ServletRequestAttributes(request);
request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
LocaleContextHolder.setLocale(request.getLocale());
RequestContextHolder.setRequestAttributes(attributes); // 保存attributes
}
}
// RequestContextHolder
/**
* Bind the given RequestAttributes to the current thread,
* <i>not</i> exposing it as inheritable for child threads.
* @param attributes the RequestAttributes to expose
* @see #setRequestAttributes(RequestAttributes, boolean)
*/
public static void setRequestAttributes(@Nullable RequestAttributes attributes) {
setRequestAttributes(attributes, false); // inheritable 默认为false
}
所以要使用如下方法重新执行一下
public class MyRequestContextListener implements ServletRequestListener {
private static final String REQUEST_ATTRIBUTES_ATTRIBUTE =
MyRequestContextListener.class.getName() + ".REQUEST_ATTRIBUTES";
/**
* 初始化
*
* @param requestEvent Information about the request
*/
@Override
public void requestInitialized(ServletRequestEvent requestEvent) {
if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {
throw new IllegalArgumentException(
"Request is not an HttpServletRequest: " + requestEvent.getServletRequest());
}
HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
ServletRequestAttributes attributes = new ServletRequestAttributes(request);
request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
LocaleContextHolder.setLocale(request.getLocale());
RequestContextHolder.setRequestAttributes(attributes, true);
}
...
}
InheritableThreadLocal也有局限性,即后续父线程数据更改的时候不会在子线程进行同步
tips: InheritableThreadLocal 由于
InheritableThreadLocal
是只有在线程初始化的时候才会进行初始化复制,所以后续父线程数据更改的时候不会进行同步,如下所示
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
// inheritThreadLocals为True时 构造一个新映射,包括来自给定父映射的所有可继承 ThreadLocal。仅由 createInheritedMap 调用。
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
由此,可以对以上方法进行升级一步到位,集成阿里的TransmittableThreadLocal
。
public class CrmRequestContextHolder {
private static final TransmittableThreadLocal<RequestAttributes> requestAttributesHolder =
new TransmittableThreadLocal<>();
public static void resetRequestAttributes() {
requestAttributesHolder.remove();
}
public static void setRequestAttributes(@Nullable RequestAttributes attributes) {
if (attributes == null) {
resetRequestAttributes();
} else {
requestAttributesHolder.set(attributes);
}
}
@Nullable
public static RequestAttributes getRequestAttributes() {
return requestAttributesHolder.get();
}
}
public class MyRequestContextListener implements ServletRequestListener {
private static final String REQUEST_ATTRIBUTES_ATTRIBUTE =
MyRequestContextListener.class.getName() + ".REQUEST_ATTRIBUTES";
/**
* 初始化
*
* @param requestEvent Information about the request
*/
@Override
public void requestInitialized(ServletRequestEvent requestEvent) {
if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {
throw new IllegalArgumentException(
"Request is not an HttpServletRequest: " + requestEvent.getServletRequest());
}
HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
ServletRequestAttributes attributes = new ServletRequestAttributes(request);
request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
LocaleContextHolder.setLocale(request.getLocale());
CrmRequestContextHolder.setRequestAttributes(attributes);
}
...
}
tips: 由于feign的线程没有被Ttl装饰,所以只有继承性,没有全局关联性,等同于InheritableThreadLocal,若需要对应功能需要确保Feign线程正确地使用了TTL装饰器。实现如下
重写HystrixConcurrencyStrategy
@Slf4j
public class CrmHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
private static final CrmHystrixConcurrencyStrategy INSTANCE = new CrmHystrixConcurrencyStrategy();
public static HystrixConcurrencyStrategy getInstance() {
return INSTANCE;
}
private CrmHystrixConcurrencyStrategy() {
}
private static ThreadFactory getThreadFactory(final HystrixThreadPoolKey threadPoolKey) {
return !PlatformSpecific.isAppEngineStandardEnvironment() ? new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(0);
public Thread newThread(Runnable r) {
// 此处使用Ttl包装
Thread thread = new Thread(TtlRunnable.get(r), "ttlHr-" + threadPoolKey.name() + "-" + this.threadNumber.incrementAndGet());
thread.setDaemon(true);
return thread;
}
} : PlatformSpecific.getAppEngineThreadFactory();
}
}
重置策略
HystrixPlugins.getInstance().registerConcurrencyStrategy(CrmHystrixConcurrencyStrategy.getInstance());
查看 效果