dubbo处理自定义异常
背景
在实际项目中,我们不可避免地需要使用自定义的异常,一般这个异常会继承RuntimeException,然后我们通过@RestControllerAdvice注解,拦截业务异常类,做一些处理,但是在使用dubbo构建项目时,会发现provider抛出自定义异常,然后在消费者端,不会捕捉到我们的自定义异常,而是以RuntimeException的形式被捕获。
原因
dubbo的异常处理类是org.apache.dubbo.rpc.filter.ExceptionFilter类,在这个类中,我们查看它的源码如下:
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.apache.dubbo.rpc.filter;
import java.lang.reflect.Method;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.ReflectUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.rpc.Filter;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.BaseFilter.Listener;
import org.apache.dubbo.rpc.service.GenericService;
@Activate(
group = {"provider"}
)
public class ExceptionFilter implements Filter, Listener {
private Logger logger = LoggerFactory.getLogger(ExceptionFilter.class);
public ExceptionFilter() {
}
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
return invoker.invoke(invocation);
}
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
try {
Throwable exception = appResponse.getException();
if (!(exception instanceof RuntimeException) && exception instanceof Exception) {
return;
}
try {
Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
Class<?>[] exceptionClasses = method.getExceptionTypes();
Class[] var7 = exceptionClasses;
int var8 = exceptionClasses.length;
for(int var9 = 0; var9 < var8; ++var9) {
Class<?> exceptionClass = var7[var9];
if (exception.getClass().equals(exceptionClass)) {
return;
}
}
} catch (NoSuchMethodException var11) {
return;
}
this.logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getServiceContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile != null && exceptionFile != null && !serviceFile.equals(exceptionFile)) {
String className = exception.getClass().getName();
if (!className.startsWith("java.") && !className.startsWith("javax.")) {
if (exception instanceof RpcException) {
return;
}
appResponse.setException(new RuntimeException(StringUtils.toString(exception)));
return;
}
return;
}
return;
} catch (Throwable var12) {
this.logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getServiceContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + var12.getClass().getName() + ": " + var12.getMessage(), var12);
}
}
}
public void onError(Throwable e, Invoker<?> invoker, Invocation invocation) {
this.logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getServiceContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
}
public void setLogger(Logger logger) {
this.logger = logger;
}
}
从上面的源码中,我们可以知道:
1. 如果provider实现了GenericService接口,直接抛出异常
2. 如果是checked异常,直接抛出
3. 在方法签名上有声明,直接抛出
4. 异常类和接口类在同一个jar包,直接抛出
5. 是JDK自带的异常,直接抛出
6. 是dubbo本身的异常,直接抛出
7. 否则,保证为RuntimeException抛出
针对上述内容,我们抛出自定义异常的实现方案有:
1)provider实现GenericService接口.(我没试过这种方式,应该是要自己实现i n v o k e ( ) 方法 , 网上说直接把 invoke()方法,网上说直接把invoke()方法,网上说直接把invoke()方法废弃掉不知道是怎么处理的,直接返回null肯定是不行的)
2)自定义异常声明为checked异常(这没啥说了,不过一般自定义异常都是unchecked)
3)在方法签名上声明抛出异常(这种基本上所有接口都要写,麻烦)
4)异常类和接口类在同一jar包里(存在链式调用时,这种可能不适用)
5)自定义异常的包名以java.或javax.开头(dubbo判断jdk自带异常的条件,一般项目都有自己的命名规范,这样干的估计很少)
6) 将dubbo源码中的ExceptionFilter复制到我们的项目名并该名为DubboExceptionFiler,然后修改源代码,遇到我们自定义的异常后,直接返回
下面我们通过演示项目,来介绍第4和第6种方法。
演示代码
创建一个maven项目,假设名为:rpcException01,pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>rpcException01</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<dubbo.version>3.0.2.1</dubbo.version>
<spring-dubbo.version>2.0.0</spring-dubbo.version>
<spring-boot-dependencies.version>2.7.0</spring-boot-dependencies.version>
<lombok.version>1.18.22</lombok.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-bom</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
在该项目下,创建一个子模块,假设名为rpc-common,用于存储我们的一些返回结果类
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>rpcException01</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>rpc-common</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
ResultCode.java,返回状态码枚举类
package com.young.common.enums;
import java.io.Serializable;
public enum ResultCode{
SUCCESS(200,"操作成功"),
FAIL(400,"操作失败"),
ERROR(500,"服务器错误");
private Integer code;
private String msg;
private ResultCode(Integer code,String msg){
this.code=code;
this.msg=msg;
}
public Integer getCode(){
return this.code;
}
public String getMsg(){
return this.msg;
}
}
ResultVO.java,返回结果类
package com.young.common.vo;
import com.young.common.enums.ResultCode;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResultVO <T> implements Serializable {
private Integer code;
private String msg;
private T data;
public ResultVO(ResultCode resultCode){
this.code=resultCode.getCode();
this.msg=resultCode.getMsg();
}
public ResultVO(ResultCode resultCode,T data){
this.code=resultCode.getCode();
this.msg=resultCode.getMsg();
this.data=data;
}
public ResultVO(Integer code,String msg){
this.code=code;
this.msg=msg;
this.data=null;
}
}
ResultVOUtil.java,返回结果工具类
package com.young.common.util;
import com.young.common.enums.ResultCode;
import com.young.common.vo.ResultVO;
public class ResultVOUtil<T> {
public static <T> ResultVO<T> success(){
return new ResultVO<T>(ResultCode.SUCCESS);
}
public static <T> ResultVO<T> success(T data){
return new ResultVO<T>(ResultCode.SUCCESS,data);
}
public static <T> ResultVO<T> fail(){
return new ResultVO<T>(ResultCode.FAIL);
}
public static <T> ResultVO<T> fail(ResultCode resultCode){
return new ResultVO<T>(resultCode);
}
public static <T> ResultVO<T> error(){
return new ResultVO<T>(ResultCode.ERROR);
}
}
BusinessException.java,自定义异常类
package com.young.common.err;
import com.young.common.enums.ResultCode;
public class BusinessException extends RuntimeException{
private ResultCode resultCode;
public BusinessException(){}
public BusinessException(ResultCode resultCode){
super(resultCode.getMsg());
this.resultCode=resultCode;
}
public ResultCode getResultCode(){
return this.resultCode;
}
}
再创建一个模块,假设名为dubbo-api,用于定义接口
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>rpcException01</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>rpc-api</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>rpc-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
TestService.java,测试接口类
package com.young.api.service;
public interface TestService {
String hello1();
String hello2();
String hello3();
}
ApiBusinessException.java ,和接口在同一个包下,这样的话可以直接获取到自定义的异常
package com.young.api.service;
import com.young.common.enums.ResultCode;
import java.io.Serializable;
public class ApiBusinessException extends RuntimeException{
private ResultCode resultCode;
public ApiBusinessException(){
}
public ApiBusinessException(ResultCode resultCode){
super(resultCode.getMsg());
this.resultCode=resultCode;
}
public ResultCode getResultCode(){
return this.resultCode;
}
}
再创建一个模块,rpc-provider,用于提供服务
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>rpcException01</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>rpc-consumer</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>rpc-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
TestServiceImpl.java
package com.young.provider.service.impl;
import com.young.api.service.ApiBusinessException;
import com.young.api.service.TestService;
import com.young.common.enums.ResultCode;
import com.young.common.err.BusinessException;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.dubbo.rpc.filter.ExceptionFilter;
import org.apache.dubbo.rpc.service.GenericException;
import org.apache.dubbo.rpc.service.GenericService;
import org.springframework.stereotype.Service;
@Service
@DubboService
public class TestServiceImpl implements TestService {
@Override
public String hello1() {
//抛出rpc-common中的异常
throw new BusinessException(ResultCode.ERROR);
}
@Override
public String hello2() {
System.out.println("访问到hello2================");
//抛出rpc-api中的异常
throw new ApiBusinessException(ResultCode.ERROR);
}
@Override
public String hello3(){
return "hello world";
}
}
DubboExceptionFilter.java,复制ExceptionFilter的源代码,然后进行修改
package com.young.provider.filter;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.ReflectUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.rpc.*;
import org.apache.dubbo.rpc.filter.ExceptionFilter;
import org.apache.dubbo.rpc.service.GenericService;
import java.lang.reflect.Method;
/**
* 复制ExceptionFilter的源代码,然后进行修改
*/
@Activate(
group = {"provider"}
)
public class DubboExceptionFilter implements Filter, BaseFilter.Listener {
private Logger logger = LoggerFactory.getLogger(org.apache.dubbo.rpc.filter.ExceptionFilter.class);
public DubboExceptionFilter() {
}
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
return invoker.invoke(invocation);
}
public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
if (appResponse.hasException() && GenericService.class != invoker.getInterface()) {
try {
Throwable exception = appResponse.getException();
if (!(exception instanceof RuntimeException) && exception instanceof Exception) {
return;
}
try {
Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
Class<?>[] exceptionClasses = method.getExceptionTypes();
Class[] var7 = exceptionClasses;
int var8 = exceptionClasses.length;
for(int var9 = 0; var9 < var8; ++var9) {
Class<?> exceptionClass = var7[var9];
if (exception.getClass().equals(exceptionClass)) {
return;
}
}
} catch (NoSuchMethodException var11) {
return;
}
this.logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getServiceContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile != null && exceptionFile != null && !serviceFile.equals(exceptionFile)) {
String className = exception.getClass().getName();
System.out.println("className:"+className);
if (!className.startsWith("java.") && !className.startsWith("javax.")) {
//如果是自定义的异常,也直接返回
if (className.startsWith("com.young.common.err.")){
return ;
}
if (exception instanceof RpcException) {
return;
}
appResponse.setException(new RuntimeException(StringUtils.toString(exception)));
return;
}
return;
}
return;
} catch (Throwable var12) {
this.logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getServiceContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + var12.getClass().getName() + ": " + var12.getMessage(), var12);
}
}
}
public void onError(Throwable e, Invoker<?> invoker, Invocation invocation) {
this.logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getServiceContext().getRemoteHost() + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", exception: " + e.getClass().getName() + ": " + e.getMessage(), e);
}
public void setLogger(Logger logger) {
this.logger = logger;
}
}
然后在resource目录下,创建一个META-INF目录,在META-INF目录下,再创建一个dubbo目录,然后在dubbo目录下,创建一个文件,文件名为:org.apache.dubbo.rpc.Filter,文件内容为:
dubboExceptionFilter=com.young.provider.filter.DubboExceptionFilter
application.yml
server:
port: 8001
spring:
application:
name: provider-service
dubbo:
registry:
address: zookeeper://localhost:2181
protocol:
port: 29081
name: dubbo
host: 127.0.0.1
provider:
filter: dubboExceptionFilter,-exception
创建rpc-consumer模块,pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>rpcException01</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>rpc-consumer</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>rpc-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
GlobalExceptionHandler.java,全局异常处理类
package com.young.consumer.handler;
import com.young.api.service.ApiBusinessException;
import com.young.common.enums.ResultCode;
import com.young.common.err.BusinessException;
import com.young.common.util.ResultVOUtil;
import com.young.common.vo.ResultVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.rpc.filter.ExceptionFilter;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(value = BusinessException.class)
public ResultVO handlerBusiness(BusinessException e){
log.info("捕获businessException===============");
return ResultVOUtil.fail(e.getResultCode());
}
@ExceptionHandler(value = ApiBusinessException.class)
public ResultVO handlerApiBusinessException(ApiBusinessException e){
log.info("捕获apiBusinessException===============");
return ResultVOUtil.fail(e.getResultCode());
}
@ExceptionHandler(value = Exception.class)
public ResultVO handlerException(Exception e){
e.printStackTrace();
log.info("捕获exception===============");
return new ResultVO(ResultCode.FAIL.getCode(),e.getMessage());
}
}
TestController.java
package com.young.consumer.controller;
import com.young.api.service.TestService;
import com.young.common.util.ResultVOUtil;
import com.young.common.vo.ResultVO;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@DubboReference
private TestService testService;
@GetMapping("/hello1")
public ResultVO hello1(){
String s = testService.hello1();
return ResultVOUtil.success(s);
}
@GetMapping("/hello2")
public ResultVO hello2(){
String s = testService.hello2();
return ResultVOUtil.success(s);
}
@GetMapping("/hello3")
public ResultVO hello3(){
String s = testService.hello3();
return ResultVOUtil.success(s);
}
}
application.yml
server:
port: 8002
spring:
application:
name: consumer-service
dubbo:
registry:
address: zookeeper://localhost:2181
protocol:
port: 29082
name: dubbo
host: 127.0.0.1
provider:
filter: dubboExceptionFilter,-exception
完整文件目录如下图所示:
分别启动zookeeper,ProviderApplication,ConsumerApplication,分别访问/hello1, /hello2, /hello3,结果如下:
至此,我们就实现了dubbo捕获自定义异常了,这里再记录一个小坑,我们在自定义异常的时候,自定义的异常要有无参构造函数,因为dubbo默认是使用Hessian进行反序列化的,该反序列化创建对象的时候,会取参数最少的构造方法来创建对象,构造方法参数设置默认值,基本类型设置为相应基本类型的默认值,不是基本类型设置为null,如果没有无参构造方法,访问接口时,结果如下:
文章来源:https://www.toymoban.com/news/detail-452415.html
参考文章
dubbo(四)异常处理
Dubbo如何处理业务异常,这个一定要知道哦!
Dubbo通过Hessian反序列化对象失败解决文章来源地址https://www.toymoban.com/news/detail-452415.html
到了这里,关于dubbo处理自定义异常的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!