1.问题产生
这是发生在最近的一个bug,同事将幂等Id设置到API请求的header里面,然后被调用方通过判断header中的该值是否为空进行处理。不为空走幂等逻辑,为空走重新创建新数据(非幂等)的逻辑。现在有个业务需要一直走非幂等逻辑,于是将Header的key设置成null值后不能走非幂等逻辑。
2.问题分析
模拟当时的代码:
SpringBoot 和 SpringCloud的依赖:
版本较老
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
//上述版本的SpingCloud是来自NetFlix
//接收方
@RestController
public class SpringCloudController {
@GetMapping("order/byId")
public void byId(@RequestParam("id") String id, @RequestHeader("null_header") String nullHeader, @RequestHeader("order_key") String orderKey, HttpServletRequest request){
String result = request.getHeder("order_key");
System.out.println("From request:" + result);
System.out.println("order_key:" + orderKey);
System.out.println("null_header:" + nullHeader);
}
}
//发请求方
//先发请求到这个API,这个API内会调用Feign的
@RestController
@RequestMapping("/springcloud")
public class SpringCloudController {
@Autowired
private OrderInterface orderInterface;
@GetMapping("/feign")
public void testFeign(){
orderInterface.getOrderById("orderId", null, "encodeOrderKey");
}
}
//定义这个Feign 本地调用
@FeignClient(value = "ORDER", url = "http://127.0.0.1:80", fallback = OrderClient.class)
public interface OrderInterface {
@GetMapping("/order/byId")
OrderDTO getOrderById(@RequestParam("id") String id, @RequestHeader("null_header") String nullableHeader, @RequestHeader(value = "order_key", required = false) String orderKey);
}
@Component
class OrderClient implements OrderInterface{
@Override
public OrderDTO getOrderById(String id, String nullableHeader, String orderKey) {
return null;
}
}
- 当收到请求需要调用feign时,他会把参数按顺序收集到一个数组里面,数组叫做Object[] argv;
- 根据启动时注解加载的元数据创建一个RequestTemplate,该对象内部的请求参数请求头都是key –>{key}形式;
- 根据argv数组构建一个LinkedHashMap,构建时会跳过Null值,这个map时为了后期根据参数的key装载数据的;
- 先装载query数据到RequestTemplate,构建URL地址连接,装载header数据
- 装载header数据的时候调用feign.RequestTemplate#expand,下述这段代码让为null的header产生一个另一个{key}
String key = var.toString();
Object value = variables.get(var.toString());
if (value != null) {
builder.append(value);
} else {
builder.append('{').append(key).append('}');
}
换成新版本: 新版本是Spring用的自家的openFeign
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<spring.cloud-version>2021.0.4</spring.cloud-version>
<type>pom</type>
<scope>import</scope>
</dependency>
新版本和老版本的主要流程差不多,主要区别是expend方法 feign.template.HeaderTemplate#expand(新版本)
public String expand(Map<String, ?> variables) {
List<String> expanded = new ArrayList<>();
if (!this.values.isEmpty()) {
for (Template template : this.values) {
String result = template.expand(variables);
if (result == null) { //如果获取不到对应的value时 不对该header的key进行塞值
/* ignore unresolved values */
continue;
}
expanded.add(result);
}
}
StringBuilder result = new StringBuilder();
if (!expanded.isEmpty()) {
result.append(String.join(", ", expanded));
}
return result.toString();
}
虽然新版本对为null的header的不设置值,但是接收方如果使用@RequestHeader接收时一定要要把required的属性设置成false,不然会报错。或者通过HttpServletRequest的getHeader(“key”)的方法来获取。
PREVIOUS自定义工具类消除if-else实现链式编程
NEXT注解与反射