上周处理了一个线上问题,通过排查发现是RPC远端调用超时,框架抛出的超时异常没有被捕捉,致使数据进入中间态,没法推动后续处理。好在影响不大,及时修复掉了。
关于这部分的代码规范,以前也有所思考,正好有这个契机作一下整理。java
作应用分层架构时,有一种实践方式是将表明外部服务的类如UserService,包装成一个UserServiceClient类,上层业务调用统一使用UserServiceClient,是一种简单的代理模式。
本文的讨论实例,即UserService、UserServiceClient以及其实现UserServiceClientImpl,形式化的定义以下:spring
// 远程RPC接口 public interface UserService { /** * 用户查询 */ ResultDTO<UserInfo> query(QueryRequest param); /** * 用户建立 */ ResultDTO<String> create(CreateRequest param); }
// 本地接口 public interface UserServiceClient { /** * 用户查询 */ Result<UserInfo> query(QueryReequest param); /** * 用户建立 */ Result<String> create(CreateRequest param); }
// 本地接口实现 public classe UserServiceClientImpl implement UserServiceClient { @Autorwire private UserService userSerivce; /** * 用户查询 */ @override Result<UserInfo> query(QueryReequest param) { // 包装调用代码片断 } /** * 用户建立 */ @override Result<String> create(CreateRequest param) { // 包装调用代码片断 } }
Client类没有任何的处理,仅仅是对Servie类的调用及原样返回。架构
// 本地接口实现 public classe UserServiceClientImpl implement UserServiceClient { @Autorwire private UserService userSerivce; /** * 用户查询 */ @override Result<UserInfo> query(QueryReequest param) { return userSerivce.query(param); } /** * 用户建立 */ @override Result<String> create(CreateRequest request) { return userSerivce.create(param); } }
很是不推荐,缘由能够和后续的几种形式中对比来看。框架
这种写法实际上跟lombok提供的@Delegate注解是同样的,这个注解同样不推荐。ide
@Component public class UserServiceClient { @Autowired @Delegate private UserService userService; }
RPC调用的目标多是不一样的系统,调用的封装结果也有所不一样。为了便于上层业务处理,减小对外部的感知,能够定义一个通用的Result类来包装。编码
// 本地接口实现 public classe UserServiceClientImpl implement UserServiceClient { @Autorwire private UserService userSerivce; /** * 用户查询 */ @override Result<UserInfo> query(QueryReequest param) { ResultDTO<UserInfo> rpcResult = userSerivce.query(param); Result<UserInfo> result = new Result<>(); // 封装调用结果 result.setSuccess(result.isSuccess()); result.setData(result.getData()); // 错误码、错误堆栈等填充,略 return result; } /** * 用户建立 */ @override Result<String> create(CreateRequest request) { // 略 } }
上层处理时,对封装的结果判断会比较冗余。若是在Client就能区分使用意图,能够将非预期的结果封装成业务异常,预期结果直接返回。
特定场景的返回结果能够用不一样的业务异常区分。代理
// 本地接口实现 public classe UserServiceClientImpl implement UserServiceClient { @Autorwire private UserService userSerivce; /** * 用户查询 */ @override UserInfo query(QueryReequest param) { ResultDTO<UserInfo> rpcResult = userSerivce.query(param); if(rpcResult == null) { throw new BizException("调用结果为空!"); } if(rpcResult != null && rpcResult.isSuccess()) { return rpcResult.getData(); } if("XXX".equals(rpcResult.getErrorCode())) { throw new XXXBizException("调用结果失败,异常码XXX"); } else { throw new BizException("调用结果失败"); } } /** * 用户建立 */ @override String create(CreateRequest request) { // 略 } }
RPC调用会发生系统间交互,不免会出现超时,不少框架直接抛出超时异常。除此之外,被调用的业务系统接口可能因为历史缘由或者编码问题,可能会直接把本身的异常抛给调用者。为了保证本身系统的稳定性,须要对异常进行捕获。
如何捕获异常?并非简单的catch(Exception e)
就能搞定。在阿里巴巴出品的《Java开发手册》中提到,要用Throwable来捕获,缘由是:日志
【强制】在调用 RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用 Throwable
类来进行拦截。
说明:经过反射机制来调用方法,若是找不到方法,抛出 NoSuchMethodException。什么状况会抛出
NoSuchMethodError 呢?二方包在类冲突时,仲裁机制可能致使引入非预期的版本使类的方法签名不匹
配,或者在字节码修改框架(好比:ASM)动态建立或修改类时,修改了相应的方法签名。这些状况,即
使代码编译期是正确的,但在代码运行期时,会抛出 NoSuchMethodError。代码规范
这样,一个完善的Client就完成了:code
// 本地接口实现 public classe UserServiceClientImpl implement UserServiceClient { @Autorwire private UserService userSerivce; /** * 用户查询 */ @override UserInfo query(QueryReequest param) { try { ResultDTO<UserInfo> rpcResult = userSerivce.query(param); } catch (Throwable t) { if(t instanceof XXXTimeoutException) { // 已知的特殊调用异常处理,如超时异常须要作自动重试,特殊处理 throw new BizException("超时异常") } throw new BizException("调用异常", t) } if(rpcResult == null) { throw new BizException("调用结果为空!"); } if(rpcResult != null && rpcResult.isSuccess()) { return rpcResult.getData(); } if("XXX".equals(rpcResult.getErrorCode())) { throw new XXXBizException("调用结果失败,异常码XXX"); } else { throw new BizException("调用结果失败"); } } /** * 用户建立 */ @override String create(CreateRequest request) { // 略 } }
对于外部调用,以及内部调用,均可以用拦截器作统一的处理。对于捕获的异常的处理以及日志的打印在拦截器中作,会让代码编写更加简洁。
示例以下:
import java.lang.reflect.Method; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class RpcInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); String invocationSignature = method.getDeclaringClass().getSimpleName() + "." + method.getName(); // 排除掉java原生方法 for(Method m : methods) { if(invocation.getMethod().equals(m)) { return invocation.proceed(); } } Object result = null; Objectp[] params = invocation.getArguments(); try { result = invocation.proceed(); } catch( Throwable e) { // 接各类异常,区分异常类型 // 处理异常、打印日志 } finally { // 打印结果日志, 打印时也要处理异常 } return result; }
设置代理
import org.slf4j.LoggerFactory; import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator; import org.springframework.boot.autoconfigure.condition.ConitionalOnBean; import org.springframework.context.ApplicationContext; import org.springframework.context.Bean; import org.springframework.context.ComponentScan; import org.springframework.context.Configuration; @Configuration public class Interceptor { @Bean public RpcInterceptor rpcSerivceInterceptor() { RpcInterceptor rpcSerivceInterceptor = new RpcInterceptor(); // 能够注入一些logger什么的 return rpcSerivceInterceptor; } @Bean public BeanNameAutoProxyCreator rpcServiceBeanAutoProxyCreator() { BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator(); // 设置代理类的名称 beanNameAutoProxyCreator.setBeanNames("*RpcServiceImpl"); // 设置拦截链名字 beanNameAutoProxyCreator.setInterceptorName("rpcSerivceInterceptor"); return beanNameAutoProxyCreator; } }
若是对上层的返回结果须要统一封装,也能够在拦截器里作。