【Spring Cloud】feign调用携带token

【Spring Cloud】feign调用携带token

码农世界 2024-06-06 后端 80 次浏览 0个评论

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

查看 效果

【Spring Cloud】feign调用携带token

转载请注明来自码农世界,本文标题:《【Spring Cloud】feign调用携带token》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,80人围观)参与讨论

还没有评论,来说两句吧...

Top