一、背景
我们在自己的业务系统中,通常会用到自定义的业务异常类,这个异常会继承extends RuntimeException,当发生业务限制的时候,会throw出来。但是在使用dubbo进行soa治理的时候,会发现provider抛出的异常,在custom端并不能正确的捕获。即便我们在provider和custom都有导入相同framework.jar下面的BusinessException异常,并且抛出这个异常。下面是出错情况
(1)、provider代码
@Service
public 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:20880
com.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)、业务代码中抛出异常改成自定义异常
@Service
public 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提示了
五、最后,这个是项目的结构图