异常-捕获业务异常踩坑记录

文章讨论了在使用Google的本地缓存框架时遇到的问题,当HTTP请求失败并手动抛出DomainServiceException业务异常时,该异常被转换为ExecutionException,最终变成UncheckedExecutionException。原因是LoadingCache的内部机制将业务异常包裹在这些异常中。解决方案是通过检查异常的cause来捕获和处理业务异常。

事情是这样,用了google的一个本地缓存框架,就是在查询数据的时候如果有就取缓存,没有就发http请求调接口,但是http请求也会有查询失败的时候,查询失败就会手动抛一个业务异常,然后我会在外层各种捕获异常,通过判断异常类型来做特殊处理。结果发现,手动抛了一个异常,但是catch里面没有找到我的异常,原因是什么下面和大家分享下,顺便记录下自己的踩坑日志。

通过LoadingCache来获取内容,里面手动抛了DomainServiceException异常,但是在捕获的时候却进到了Exception这是为什么?

先说下结论:LoadingCache会把我们定义的业务异常转成ExecutionException,最后又转成了UncheckedExecutionException了,但是无论怎么转,我们的业务异常都在cause里面。

二、下面讲下LoadingCache是如何实现的

当执行LoadingCache的get方式时会进到loadFuture方法,里面判断本地缓存是否存在不存在就发http请求查询数据

1、loadFuture方法
public ListenableFuture<V> loadFuture(K key, CacheLoader<? super K, V> loader) {
    try {
        
        V previousValue = oldValue.get();
        if (previousValue == null) {
            V newValue = loader.load(key);  //1、如果缓存为空,执行具体的目标方法,来获取数据。
            return set(newValue) ? futureValue : Futures.immediateFuture(newValue);
        }
        ListenableFuture<V> newValue = loader.reload(key, previousValue);
        if (newValue == null) {
            return Futures.immediateFuture(null);
        }
        
        return transform(
                newValue,
                new com.google.common.base.Function<V, V>() {
                    @Override
                    public V apply(V newValue) {
                        LocalCache.LoadingValueReference.this.set(newValue);
                        return newValue;
                    }
                },
                directExecutor());
    } catch (Throwable t) {
        ListenableFuture<V> result = setException(t) ? futureValue : fullyFailedFuture(t);
        if (t instanceof InterruptedException) {
            Thread.currentThread().interrupt();
        }
        return result;
    }
}

因为http请求里面做了业务处理,如果没有查询到,就会报错,也就是loader.load这段代码会报错,但是上面的代码有try-catch来捕获异常,然后得到Failure对象,Failure对象里面保存了具体的业务异常,最终Failure对象被存到了SettableFuture类型对象里面。看下SettableFuture里面什么样,如下图。然后返回SettableFuture对象。

1、loadSync方法
V loadSync(K key, int hash, LoadingValueReference<K, V> loadingValueReference, CacheLoader<? super K, V> loader) throws ExecutionException {
    //查询数据,也包括从缓存获取内容,如果报错,报错对象也会在loadingFuture对象里面。
    ListenableFuture<V> loadingFuture = loadingValueReference.loadFuture(key, loader); 

    //解析loadingFuture,如果成功会把数据存到本地缓存里面,如果失败,就会把错误抛出来。
    return getAndRecordStats(key, hash, loadingValueReference, loadingFuture);
}

因为loadFuture报错了,但是还是返回了ListenableFuture对象,getAndRecordStats会解析ListenableFuture对象。

//根据对象类型来判断。是报错还是缓存数据
private V getDoneValue(Object obj) throws ExecutionException {
    if (obj instanceof AbstractFuture.Cancellation) {
        throw cancellationExceptionWithCause("Task was cancelled.", ((AbstractFuture.Cancellation) obj).cause);
    } else if (obj instanceof AbstractFuture.Failure) {
        throw new ExecutionException(((AbstractFuture.Failure) obj).exception);  //注意这里会把错误转成Google自定义的异常,我们的业务异常在cause里面。
    } else if (obj == NULL) {
        return null;
    } else {
        V asV = (V) obj;
        return asV;
    }
}

然后转成ExecutionException错误,在抛出来,但是new ExecutionException的时候已经把我么自定义的业务场景,当做参数传给了ExecutionException。

最后异常被下面代码捕获

V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
    try {
        return lockedGetOrLoad(key, hash, loader);
    } catch (ExecutionException ee) {
        Throwable cause = ee.getCause();   //拿到业务异常,判断是否Error还是RuntimeException
        if (cause instanceof Error) {
            throw new ExecutionError((Error) cause);
        } else if (cause instanceof RuntimeException) {
            throw new UncheckedExecutionException(cause);
        }
        throw ee;
    } finally {
        postReadCleanup();
    }
}

因为我们自定义的异常都是继承了RuntimeException,所以会走到UncheckedExecutionException,然后转成UncheckedExecutionException异常。

结论:我们业务自定义的异常DomainServiceException先被Google转成了ExecutionException,最后又转成了UncheckedExecutionException了,但是无论怎么转,我们的业务异常都在cause里面。

所以我们在判断业务异常,不能直接使用下图这种方式获取

而是要使用下图这种方式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

信仰_273993243

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值