【Spring Cloud】feign调用携带token

文章讲述了在Feign调用服务时如何处理需要token验证的情况,介绍了使用InheritableThreadLocal保存请求上下文以解决跨线程问题,以及如何升级至TransmittableThreadLocal以提高线程安全性和功能。同时提到了使用CrmHystrixConcurrencyStrategy重写并发策略以确保Feign线程正确使用TTL装饰器。

当我们再用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());

查看 效果
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

生如夏花般绚丽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值