RXJava处理HTTP 204空响应反序列化异常及Spring事务回滚问题求助
我之前也踩过这个坑!问题根源在于Retrofit的RxJava2适配器对HTTP 204响应的特殊处理:当服务器返回204(No Content)时,BodyObservable会把空响应转换成空字符串或直接返回null,但你的Single<MyObject>期望接收一个实体对象,这就触发了ClassCastException: String cannot be cast to MyObject异常——而Spring事务会因为这个运行时异常标记回滚,但204本身是合法的成功响应,完全没必要回滚。
下面给你几个实用的解决办法,按推荐程度排序:
方法1:改用Single<Response<MyObject>>接收完整响应
这是最直接稳妥的方案,直接拿到整个HTTP响应对象,自己判断状态码并处理body:
Single<Response<MyObject>> cp = cpApi.findOne(myparam); try { Response<MyObject> response = cp.blockingGet(); // 处理204空响应,返回null或空实例(根据业务需求选择) if (response.code() == HttpStatus.NO_CONTENT.value()) { return null; // 或者 return new MyObject(); } return response.body(); } catch (RuntimeException t) { // 这里只处理真正的错误(比如网络异常、5xx错误等) throw new RuntimeException(t.getMessage()); }
这种方式让你完全掌控响应逻辑,既不会因为204抛出异常,也能正常处理其他错误场景。
方法2:在RxJava链上捕获特定异常并处理
如果不想修改接口返回类型,可以在RxJava流中捕获204导致的ClassCastException,返回默认值(比如null):
Single<MyObject> cp = cpApi.findOne(myparam) .onErrorResumeNext(throwable -> { // 判断是否是204引发的类型转换异常 if (throwable instanceof ClassCastException && throwable.getMessage() != null && throwable.getMessage().contains("String cannot be cast to com.myproject.model.myobject")) { // 返回null或空对象,避免抛出异常 return Single.just(null); } // 其他异常继续抛出,不影响正常错误处理 return Single.error(throwable); }); try { return cp.blockingGet(); } catch (RuntimeException t) { throw new RuntimeException(t.getMessage()); }
不过这种方法依赖异常信息的字符串匹配,有点脆弱,如果Retrofit内部实现变更可能失效,所以优先推荐方法1。
方法3:自定义Converter统一处理204响应
如果项目里很多接口都有204场景,可以自定义Retrofit的Converter.Factory,全局处理空响应:
public class EmptyResponseConverterFactory extends Converter.Factory { @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { Converter<ResponseBody, ?> delegate = retrofit.nextResponseBodyConverter(this, type, annotations); return (Converter<ResponseBody, Object>) body -> { if (body.contentLength() == 0) { // 空响应直接返回null return null; } return delegate.convert(body); }; } }
然后在构建Retrofit时添加这个Converter:
Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(new EmptyResponseConverterFactory()) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build();
这个方法能全局处理所有空响应,但要注意如果某些接口必须返回非空值,可能需要额外注解来区分。
为什么原代码会抛异常?
简单来说,Retrofit的BodyObserver收到204响应时,会调用onComplete但不会触发onSuccess(因为没有body),而RxJava的Single要求必须发射一个值,所以适配器内部会把这种情况转换成空字符串,再尝试将其转为MyObject,于是就出现了String转MyObject的类型转换异常。
内容的提问来源于stack exchange,提问作者Sophian Achiba

