当我们再用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, * not 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
由此,可以对以上方法进行升级一步到位,集成阿里的TransmittableThreadLocal。
public class CrmRequestContextHolder { private static final TransmittableThreadLocalrequestAttributesHolder = 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());
查看 效果
还没有评论,来说两句吧...