本文共 7282 字,大约阅读时间需要 24 分钟。
一、背景
我们在自己的业务系统中,通常会用到自定义的业务异常类,这个异常会继承extends RuntimeException,当发生业务限制的时候,会throw出来。但是在使用dubbo进行soa治理的时候,会发现provider抛出的异常,在custom端并不能正确的捕获。即便我们在provider和custom都有导入相同framework.jar下面的BusinessException异常,并且抛出这个异常。下面是出错情况
(1)、provider代码
@Servicepublic class UserServiceImpl implements UserService{ @Autowired private GeneralDAO dao; /** * 登录验证 * */ @Override public void loginValid(UserVO userVO) { if (dao.queryObject("userMap.getUserList",userVO)==null) throw new BusinessException("账户或者密码错误1..."); }}(2)、控制台错误
11:39:19.041 [http-nio-8002-exec-9] ERROR com.chentian610.framework.BaseController - Failed to invoke the method loginValid in the service com.chentian610.user.service.UserService. Tried 3 times of the providers [192.168.3.243:20880] (1/1) from the registry 127.0.0.1:2181 on the consumer 192.168.3.243 using the dubbo version 2.8.4a. Last error is: Invoke remote method timeout. method: loginValid, provider: dubbo://192.168.3.243:20880/com.chentian610.user.service.UserService?anyhost=true&application=web-custom&check=false&dubbo=2.8.4a&generic=false&interface=com.chentian610.user.service.UserService&logger=slf4j&methods=loginValid&owner=chentian610&pid=8732&side=consumer×tamp=1481858649300, cause: Waiting server-side response timeout. start time: 2016-12-16 11:39:18.039, end time: 2016-12-16 11:39:19.040, client elapsed: 0 ms, server elapsed: 1001 ms, timeout: 1000 ms, request: Request [id=9, version=2.0.0, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=loginValid, parameterTypes=[class com.chentian610.user.vo.UserVO], arguments=[com.chentian610.user.vo.UserVO@20705b10], attachments={path=com.chentian610.user.service.UserService, interface=com.chentian610.user.service.UserService, version=0.0.0}]], channel: /192.168.3.243:13378 -> /192.168.3.243:20880com.alibaba.dubbo.rpc.RpcException: Failed to invoke the method loginValid in the service com.chentian610.user.service.UserService. Tried 3 times of the providers [192.168.3.243:20880] (1/1) from the registry 127.0.0.1:2181 on the consumer 192.168.3.243 using the dubbo version 2.8.4a. Last error is: Invoke remote method timeout. method: loginValid, provider: dubbo://192.168.3.243:20880/com.chentian610.user.service.UserService?anyhost=true&application=web-custom&check=false&dubbo=2.8.4a&generic=false&interface=com.chentian610.user.service.UserService&logger=slf4j&methods=loginValid&owner=chentian610&pid=8732&side=consumer×tamp=1481858649300, cause: Waiting server-side response timeout. start time: 2016-12-16 11:39:18.039, end time: 2016-12-16 11:39:19.040, client elapsed: 0 ms, server elapsed: 1001 ms, timeout: 1000 ms, request: Request [id=9, version=2.0.0, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=loginValid, parameterTypes=[class com.chentian610.user.vo.UserVO], arguments=[com.chentian610.user.vo.UserVO@20705b10], attachments={path=com.chentian610.user.service.UserService, interface=com.chentian610.user.service.UserService, version=0.0.0}]], channel: /192.168.3.243:13378 -> /192.168.3.243:20880 at com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:108) ~[dubbo-2.8.4a.jar:2.8.4a] at com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:227) ~[dubbo-2.8.4a.jar:2.8.4a] at com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:72) ~[dubbo-2.8.4a.jar:2.8.4a] at com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:52) ~[dubbo-2.8.4a.jar:2.8.4a] at com.alibaba.dubbo.common.bytecode.proxy0.loginValid(proxy0.java) ~[dubbo-2.8.4a.jar:2.8.4a](3)、页面直接报错提示 二、我们查看dubbo中异常处理代码
if (result.hasException() && GenericService.class != invoker.getInterface()) { try { Throwable exception = result.getException(); // 如果是checked异常,直接抛出 if (! (exception instanceof RuntimeException) && (exception instanceof Exception)) { return result; } // 在方法签名上有声明,直接抛出 try { Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes()); Class [] exceptionClassses = method.getExceptionTypes(); for (Class exceptionClass : exceptionClassses) { if (exception.getClass().equals(exceptionClass)) { return result; } } } catch (NoSuchMethodException e) { return result; } // 未在方法签名上定义的异常,在服务器端打印ERROR日志 logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception); // 异常类和接口类在同一jar包里,直接抛出 String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface()); String exceptionFile = ReflectUtils.getCodeBase(exception.getClass()); if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)){ return result; } // 是JDK自带的异常,直接抛出 String className = exception.getClass().getName(); if (className.startsWith("java.") || className.startsWith("javax.")) { return result; } // 是Dubbo本身的异常,直接抛出 if (exception instanceof RpcException) { return result; } // 否则,包装成RuntimeException抛给客户端 return new RpcResult(new RuntimeException(StringUtils.toString(exception))); } catch (Throwable e) { logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e); return result; } }发现,当异常类和接口类在一个jar下面的时候,会使用自定义的异常类抛出,否则dubbo会自定义封装。
三、解决方案
那么就好办了,既然不想provider和custom有耦合,只需要在接口类中再定义自己的模块异常类来集成公共的BusinessException,比如UserException,
(1)、自定义业务异常类,继承公共业务异常类
/** * 用户模块自定义异常,防止消费端捕获不到异常 * Created by chentian610 on 2016-12-16 . */public class UserExcepiton extends BusinessException { public UserExcepiton(String message) { super(message); }}(2)、业务代码中抛出异常改成自定义异常
@Servicepublic class UserServiceImpl implements UserService{ @Autowired private GeneralDAO dao; /** * 登录验证 * */ @Override public void loginValid(UserVO userVO) { if (dao.queryObject("userMap.getUserList",userVO)==null) throw new UserExcepiton("账户或者密码错误,处理后..."); }}(3)、这个时候登录返回的就是正常的JSON提示了
五、最后,这个是项目的结构图