我们在定义微服务接口的时候,通常会使用一个Result类进行封装,将提示信息,返回对象和状态码等内容封装到一起返回给调用方,例如如下的格式:
public class Result<T> {
/**
* 响应码,200为成功
*/
private Integer code;
/**
* 失败时的具体失败信息
*/
private String message;
/**
* 失败信息是否为用户提示,如果是的话框架不会主动拦截该错误
*/
private boolean userPrompt;
/**
* 响应的具体对象
*/
private T data;
}
而调用方在使用Spring Cloud OpenFeign定义的客户端调用远程服务时,默认的解码器只能按照定义的方法返回类型对接口的返回结果进行强制转换,没办法实现一些自定义的逻辑,比如将统一返回的Result类重新拆开,仅返回对应的业务对象,或者对特定的响应码进行处理等等。
为了实现上述功能,我们就需要改造默认的Decoder。Spring Cloud OpenFeign允许我们在定义一个Feign Client的时候,指定一个额外的配置类,比如:
@FeignClient(name = "stores", configuration = CustomizedConfiguration.class)
public interface StoreClient {
//..
}
而这个配置类就可以作为我们的扩展点,我们可以在CustomizedConfiguration中定义一个自己的Decoder来覆盖默认的配置。Spring Cloud对Feign的封装和默认配置可以查看官方文档。自定义的Decoder需要实现feign.codec.Decoder接口,也可以参考默认的Decoder的实现逻辑(org.springframework.cloud.openfeign.support.ResponseEntityDecoder),下面的实现可以对统一返回值Result类的拆分,并对异常返回进行处理:
public class FeignResultDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
if (response.body() == null) {
throw new DecodeException(response.status(), "没有返回有效的数据", response.request());
}
String bodyStr = Util.toString(response.body().asReader(Util.UTF_8));
//对结果进行转换
Result result = (Result) JsonUtil.json2obj(bodyStr, type);
//如果返回错误,且为内部错误,则直接抛出异常
if (result.getCode() != ResultCode.SUCCESS.code) {
if (!result.isUserPrompt()) {
throw new DecodeException(response.status(), "接口返回错误:" + result.getMessage(), response.request());
}
}
return result.data;
}
}
其中如何将response中的json转换为实际的返回类型很费了一些功夫,因为可能存在复杂的嵌套关系和泛型定义,最后才发现jackson框架已经有默认的实现了... 代码如下:
public static <T> T json2obj(String jsonStr, Type targetType) {
try {
JavaType javaType = TypeFactory.defaultInstance().constructType(targetType);
return mapper.readValue(jsonStr, javaType);
} catch (IOException e) {
throw new IllegalArgumentException("将JSON转换为对象时发生错误:" + jsonStr, e);
}
}
实现了Decoder之后,只需要将其配置到CustomizedConfiguration中即可,注意如果CustomizedConfiguration添加了@Configuration的注解,则会成为Feign Client构建的默认配置,这样就不需要在每个@FeignClient注解中都去指定配置类了:
public class CustomizedConfiguration{
@Bean
public Decoder feignDecoder() {
return new FeignResultDecoder();
}
}
添加了自定义的Decoder之后,如果一个远程接口的定义是这样的:
@GetMapping("/api/user/detail")
public Result<User> detailById(@RequestParam("id") Integer id) {
User user = userService.getById(id);
return ResultGenerator.genSuccessResult(user);
}
我们定义Feign Client方法的时候,方法的返回值可以直接使用Result的泛型类型:
@RequestMapping(value = "/api/user/detail", method = RequestMethod.GET)
public User detailById(@RequestParam("id") Integer id)
类似的方式,我们还可以自定义Feign的Encoder,ErrorDecoder等关键配置组件。