事情是这样,用了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里面。
所以我们在判断业务异常,不能直接使用下图这种方式获取
而是要使用下图这种方式