【SpringCloud实战】一次开发中使用Feign添加动态Header问题思考

一壶清酒 2020-04-14 16:50:50 ⋅ 1988 阅读

需求背景

需求背景

最近有个需求,是对接某个运动APP的Api开放平台用户授权业务。文中以两个API为例:

1、获取token

场景:用户在授权⻚页⾯面点击授权后,⻚页⾯面会跳转到合作⽅方提供的redirect_url,合作⽅方通过跳转传回的code换取token,完成认证和授权。

Header附加参数:Authorization:Basic base64(AppKey:AppSecret) `

注意:Basic后⾯面必须有一个空格。

2、获取用户资料

场景:获取用户资料接⼝⽤于获取用户在APP的用户资料。

Header附加参数:Authorization:Bearer token

注意:Bearer后⾯面必须有一个空格。

分析

从上述API资料里面分析得出要对接这两个API需要设置不同的Header信息,那么就需要代码中支持动态设置header功能。

API

项目框架

由于项目是使用SpringCloud 集成Feign搭建的基础框架,并且在项目中已经设计了全局的Header。

通过实现RequestInterceptor接口,完成对所有的Feign请求,设置Header代码如下:

@Configuration
public class FeignClientConfig implements RequestInterceptor {
@Value("${token}")
private String token;

@Override
public void apply(RequestTemplate template) {
template.header("token", token);
}
/*打印feign请求日志级别*/
@Bean
public Logger.Level level() {
return Logger.Level.FULL;
}
}

原理:@FeignClient 的代理类在执行的时候,会去使用该拦截器,然后注入到 spring 上下文中,这样就可以在请求的上下文中添加自定义的请求头。

优点:所以自定义自己的拦截器

缺点:操作的是全局的 RequestTemplate,比较难以根据不同的服务方提供不同的 header。

方案一:使用Feign官方方案

经过分析得出现在的代码是不支持这次需求,那么项目中既然集成了OpenFeign,可以从这里入手看看官方是如何解决的。

OpenFeign源码地址:https://github.com/OpenFeign/feign

// openfeign 官方文档代码示例
public interface ContentService {
@RequestLine("GET /api/documents/{contentType}")
@Headers("Accept: {contentType}")
String getDocumentByType(@Param("contentType") String type);
}

通过上面的官方文档代码示例发现不需要那么麻烦,用原生的@Headers 注解就能解决我们的问题了,添加到代码上。

@Headers 代码示例

@FeignClient(name = "xxx-feign-service",url = "IP:端口")
@Headers({"Authorization: ${token}"})
public interface FeignClient {
@RequestMapping(value = "/getToken")
String getToken();
}

使用{token} 可以传递动态header属性。

一番折腾后发现@Headers 没有生效,在生成的RequestTemplate中,没有获取到token值。然后调试一下代码,发现,ReflectFeign在生成远程服务的代理类的时候,会通过 Contract 接口准备数据。而*@Headers* 注解没有生效的原因是:官方的 Contract 没有生效:

代码如下:

class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
protected Feign.Builder feign(FeignContext context) {
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
...
}
}


去翻一下springcloud-openfeign在创建 Feign 相关类的时候,使用的是容器中注入的 Contract代码如下:

@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}
public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {
@Override public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method)
{
....
// 注意这里,它只取了 RequestMapping 注解
RequestMapping classAnnotation = findMergedAnnotation(targetType, RequestMapping.class);
....
parseHeaders(md, method, classAnnotation);
}
return md;
}

我们来总结一下:

1、openfeign本身是支持在方法上使用@Header 注解,来实现自定义header功能。

2、springcloud-openfeign只是集成了openfeign的核心功能,@Headers 注解并没有被使用。

3、SpringCloud 使用了自己的 SpringMvcContract 来处理请求的相关资源信息,里面只使用 @RequestMapping 注解。

也就是被阉割了呗~~~~

方案二:使用 @RequestMapping的 headers 属性

上面提到了第3点可以使用 @RequestMapping 注解中的headers属性来解决,来试一下。

@FeignClient(name = "xxx-feign-service",url = "127.0.0.1:8080")
public interface FeignClient {
@RequestMapping(value = "/getToken")
String getToken(@Param("token") String token);
}

经测试SpringCloud 支持@RequestMapping 注解的 header,可以正常获取头部信息。

但是问题来了,很多同学不习惯在类上直接使用 @RequestMapping 注解,有没有一个全局管理的地方?也方便代码维护呢?

方案三:重写RequestInterceptor的apply方法


上面提到过通过实现RequestInterceptor接口完成对所有的Feign请求,可不可以在FeignClientConfig文件里面统一管理呢?那就需要我们自定义这个类了。具体代码如下:

@Configuration
@Data
public class FeignClientConfig implements RequestInterceptor {
@Value("${appKey}")
private String appkey;
@Value("${appSecret}")
private String appSecret;
private String token;
@Bean
public RequestInterceptor RequestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
String text = appkey+":"+appSecret;
String authorization = "";
if(StringUtils.isBlank(token)){
authorization="Basic "+ StringUtil.encode(text);
}else{
authorization="Bearer "+ token;
}
template.header("Authorization", authorization);
}
};
}
/*打印feign请求日志级别*/
@Bean
public Logger.Level level() {
return Logger.Level.FULL;
}
}

注释:根据文中开始部分提到的业务中有两个API,并且他们的请求Header信息不一样,通过重写RequestInterceptorapply方法来封装header值,达到解决动态参数的问题(有心的同学这里的代码可以更加优雅一点儿)。

总结

通过寻找解决问题的方法发现SpringMvcContract 是在 parseAndValidatateMetadata 中解决在类上面的 header 的问题,这里也特别提醒一下各位同学Spring Cloud 并没有基于Spring MVC 全部注解来做Feign 客户端注解协议解析,这个是一个不小的坑。这也导致了最开始没写想到在feign接口上使用 @RequestMapping来解决问题。

那么使用@RequestMapping 解决header问题是最简单也更加原生的方案,通过重写RequestInterceptorapply方法来实现可以统一管理头部信息,方便后续的维护两者各有千秋。



全部评论: 0

    我有话说:

    DDDplus 1.0.2 发布,轻量级业务开发框架

    DDDplus 简介 套轻量级业务开发框架,以DDD思想为本,致力于业务资产的可沉淀可传承,全方位解决复杂业务场景的扩展问题实现台核心要素,赋能台建设。 融合了前台复杂生态协作方法论

    开源资讯】JWCloud 专业版 v1.0.0 发布,基于 SpringCloud 研发的微服务框架

    简介 JavaWeb_Cloud 微服务平台是款基于 SpringCloud 框架研发的分布式微服务框架,主要使用技术栈包括: SpringCloud、Vue、ElementUI

    SpringCloud常见面试题(2020最新版)

    用户应用,在业务初期都很简单,我们通常会把它实现为...

    DDDplus 1.1.0 发布,轻量级业务开发框架

    DDDplus是套轻量级业务开发框架,以DDD思想为本,致力于业务资产的可沉淀可传承,全方位解决复杂业务场景的扩展问题实现台核心要素,赋能台建设。 融合了前台复杂生态协作方法论,充分

    前端实战篇—在Javascript,Number类型超长问题详解

    今天给大家分享的是在Javascript,获取到数字超出长度问题

    SpringBoot2.0填坑():使用CROS解决跨域并解决swagger 访问不了问题

    公司后台是采用SpringBoot2.0 搭建的微服务架构,前端框架用的是vue 使用前后端分离的开发方式,在开发联调的时候需要进行跨域访问,那么使用CROS解决了跨域问题,但是swagger 却用

    「转载」使用DDD指导业务设计的一点思考

    领域驱动设计(DDD) 是 Eric Evans 提出的种软件设计方法和思想,主要解决业务系统的设计和建模。DDD 有大量难以理解的概念,尤其是翻译的原因,某些词汇非常生涩,例如:模型、限界上下文

    WebMIS 1.0.0 beta.3 发布,全栈开发基础框架

    ,为企业提供套完整的技术解决方案,满足快速开发...

    微型Java开发框架Solon 1.1发布,QPS达10万+

    简介 Solon 是一个微型的Java开发框架。项目从2018年启动以来,参考过大量前人作品;历时两年,2700多次的commit;内核保持0.1m的身材,超高的Web跑分,良好的使用体验

    微信小程序实战篇:如何解决https域名问题

    开发自己的微信小程序绕不开https问题,为了能在小程序调用我们自己的API服务请打开看看吧!!!

    消息队列常见问题):生产上消息队列产生大量的消息堆积会有什么后果?

    大多数消息堆积原因是Consumer出现了问题,并且没有被运维/开发监控到即使修复问题,导致大量的消息都积压在 MQ ,那么会造成哪些后果呢?1、消息被丢弃例如 RabbitMQ 条消息设置

    全网最详细的SpringCloud总结

    什么是Spring cloud 构建分布式系统不需要复杂和容易出错。Spring Cloud 为最常见的分布式系统模式提供了种简单且易于接受的编程模型,帮助开发人员构建有弹性的、可靠的、协调的

    「转载」微服务分布式架构,如何实现日志链路跟踪?

    背景 开发排查系统问题用得最多的手段就是查看系统日志,在分布式环境一般使用ELK来统一收集日志,但是在并发大时使用日志定位问题还是比较麻烦,我们来看下面的图     上图

    开源资讯】Ant Design 4.8.5 发布,修复组件不能渲染等问题

    Ant Design 4.8.5 发布了。Ant Design 是套企业级的 UI 设计语言和 React 实现使用 TypeScript 构建,提供完整的类型定义文件,自带提炼自企业级后台

    开源推荐】基于 Go 语言的轻量级高性能日志库 logit使用及测评

    logit 是一个简单易用并且是基于级别控制的日志库,可以应用于所有的 GoLang 应用程序

    Fes.js v0.4.1 版本发布,套优秀的后台系统前端解决方案

    Fes.js 是套优秀的后台前端解决方案。提供初始项目、开发调试、Mock接口、编译打包的命令行工具。内置布局、权限、数据字典、状态管理、存储、Api等多个模块。以约定、配置化、组件化的设计思想

    架构实战篇:MyBatis一级、二级,并整合ehcache分布式缓存的使用,附演示实例

    ehcache是一个纯Java的进程内缓存框架,是种广泛使用开源Java分布式缓存,具有快速、精干等特点,是Hibernate默认的CacheProvider。

    WebSocket的了解,附GitHub地址

    初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?