上周处理了一个线上问题, 经过排查发现是 RPC 远端调用超时, 框架抛出的超时异常没有被捕捉, 导致数据进入中间态, 无法推进后续处理. 好在影响不大, 及时修复掉了.
关于这部分的代码规范, 之前也有所思考, 正好有这个契机做一下整理.
讨论背景和范围
做应用分层架构时, 有一种实践方式是将代表外部服务的类如 UserService, 包装成一个 UserServiceClient 类, 上层业务调用统一使用 UserServiceClient, 是一种简单的代理模式.
本文的讨论实例, 即 UserService,UserServiceClient 以及其实现 UserServiceClientImpl, 形式化的定义如下:
- // 远程 RPC 接口
- public interface UserService {
- /**
- * 用户查询
- */
- ResultDTO<UserInfo> query(QueryReequest 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 注解是一样的, 这个注解一样不推荐.
- @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 就完成了:
- // 本地接口实现
- 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;
- }
- }
如果对上层的返回结果需要统一封装, 也可以在拦截器里做.
来源: https://www.cnblogs.com/wuyuegb2312/p/11263014.html