概述
在目前微服务流行的年代,稍微大点的项目都会使用微服务架构模式。本文主要记录springcloud远程调用返回结果的异常统一处理。
应用篇
假设微服务的返回结果有如下封装:
@Data
public class ApiResponse<T> {
/**
* 返回码 0 为成功 其他为异常
*/
private int code;
/**
* 异常提示信息
*/
private String message;
/**
* 返回的数据
*/
private T result;
}
在使用微服务进行远程调用后得到ApiResponse实例,都需要判断返回结果的code,如果是成功,则继续后续处理,如果是失败,则抛出异常,终止程序向下运行,还有可能需要回滚事务(分布式事务这里不做介绍),那么有没有办法在框架层面来完成这个操作呢?
答案是肯定的。(假设微服务的返回统一为json格式)
springcloud远程调用统一返回处理
代码如下:
@Component
public class MyResponseEntityDecoder implements Decoder,SmartInitializingSingleton {
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
private ResponseEntityDecoder responseEntityDecoder;
@Override
public Object decode(final Response response, Type type) throws IOException,
FeignException {
//返回码不为200,表示远程访问有运行时异常,则直接抛出异常即可
if (response.status() ==200) {
//充分利用spring框架提供的解析器
Object result = responseEntityDecoder.decode(response, type);
ApiResponse baseResponse = (ApiResponse)result;
int code = baseResponse.getCode();
if (code==0){
return baseResponse;
}
throw new RuntimeException("异常返回");
}
throw new RuntimeException("异常返回");
}
@Override
public void afterSingletonsInstantiated() {
//初始化spring提供的解析器
responseEntityDecoder = new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
}
}
在Feign进行远程调用后,需要对结果进行解码成具体的Java对象,如ApiResponse对象。而解码操作的类,必须实现Decoder接口,并重新decode方法,最后需要让其注入到spring的bean工厂中。
通过查看springcloud源代码,发现是一个叫ResponseEntityDecoder的解析器进行解码的(ResponseEntityDecoder也实现了Decoder接口),为了充分利用这个解析器,上述自定义的解析器对ResponseEntityDecoder进行了适配。
请求端代码如下
@RequestMapping("/client")
@Controller
public class ClientCtroller {
private transient Logger log = LoggerFactory.getLogger(this.getClass());
@Resource
private UserService userService;
@PostMapping("api1")
@ResponseBody
public ApiResponse<UserDto> api1(@RequestBody UserDto UserDto) {
ApiResponse<UserDto> dt = userService.api1(UserDto);
return dt;
}
@PostMapping("api2")
@ResponseBody
public ApiResponse<List<UserDto>> callbackApi2(@RequestBody UserDto UserDto) {
ApiResponse<List<UserDto>> dt = userService.api2(UserDto);
return dt;
}
}
接口代码
@FeignClient(name = "userService", url = "${server.service.url:}", path = "/server")
public interface UserService {
@PostMapping("api1")
ApiResponse<UserDto> api1(@RequestBody UserDto UserDto);
@PostMapping("api2")
ApiResponse<List<UserDto>> api2(@RequestBody UserDto UserDto);
}
服务端代码
@RequestMapping("/server")
@Controller
public class ServerController {
@PostMapping("api1")
@ResponseBody
public ApiResponse<UserDto> api1(@RequestBody UserDto UserDto) {
ApiResponse<UserDto> dt = new ApiResponse<>();
dt.setCode(0);
dt.setMessage("successs");
dt.setResult(UserDto);
return dt;
}
@PostMapping("api2")
@ResponseBody
public ApiResponse<List<UserDto>> api2(@RequestBody UserDto UserDto) {
ApiResponse<List<UserDto>> dt = new ApiResponse<>();
dt.setCode(0);
dt.setMessage("successs");
List<UserDto> list = new ArrayList<>();
list.add(UserDto);
list.add(UserDto);
dt.setResult(list);
return dt;
}
}
其他代码
@Data
public class UserDto {
private int id;
private String name;
private String pwd;
}
注意事项
既然ApiResponse是一个泛型类,那么在系统中所有的地方,只要用到了ApiResponse类,都要使用泛型,否则在客户端获取到的数据中,ApiResponse类的result属性值就可能不是泛型对应的实际类型。
源码解析篇
对于我们自定义的被@FeignClient修饰的接口,在spring启动的时候,将会被扫描并为其生成一个代理对象,并将代理对象注入到spring的bean工厂中。
动态代理类为FeignInvocationHandler,源码如下:
static class FeignInvocationHandler implements InvocationHandler {
//目标对象,即被@FeignClient修饰的接口
private final Target target;
//接口的方法和方法处理器 MethodHandler为SynchronousMethodHandler类型,负责真正做事
private final Map<Method, MethodHandler> dispatch;
FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object
otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
return dispatch.get(method).invoke(args);
}
}
构造SynchronousMethodHandler类时,dispatch的value为MethodHandler类型(实际上是SynchronousMethodHandler类型),SynchronousMethodHandler对象成创建比较复杂,这里暂不做介绍。
当我们调用被@FeignClient修饰的接口时,会执行代码dispatch.get(method).invoke(args);即会执行SynchronousMethodHandler类的invoke方法,具体代码如下:
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template);
} catch (RetryableException e) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
然后会执行executeAndDecode方法,这个方法代码较长,主要有如下2个功能点:
- 请求远程接口,默认为HttpURLConnection进行远程接口访问
- 返回解析结果,默认为ResponseEntityDecoder,如果要使用自定义的,必须自定义类实现Decoder接口,重写相关方法,并注入到spring工厂。如我们自定义的解码器MyResponseEntityDecoder
为了清楚的阅读源码,下述代码删除了次要的部分:
Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request = targetRequest(template);
Response response;
long start = System.nanoTime();
try {
//请求远程接口,默认为HttpURLConnection进行远程接口访问
response = client.execute(request, options);
} catch (IOException e) {
throw errorExecuting(request, e);
}
boolean shouldClose = true;
try {
// 返回结果的处理
if (logLevel != Logger.Level.NONE) {
response = logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
}
if (Response.class == metadata.returnType()) {
if (response.body() == null) {
return response;
}
if (response.body().length() == null ||
response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
shouldClose = false;
return response;
}
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
return response.toBuilder().body(bodyData).build();
}
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
return decode(response);
}
} else if (decode404 && response.status() == 404) {
return decode(response);
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
}
throw errorReading(request, response, e);
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}