SPRING 七月 19, 2020

谁动了我的异常 Plus

文章字数 5.9k 阅读约需 5 mins. 阅读次数

书接上文,在 谁动了我的异常? 中,有几个问题没说清楚,本文将继续进行说明。
为了方便,相关示例代码上传至了 https://github.com/AlphaHinex/proxy-in-spring

前情回顾

示例代码中,定义了如下内容:

ClassController 中定义了一个没有声明异常的方法 post

ProxyTest 中调用此方法时,会得到一个 UndeclaredThrowableException

Puzzle 1

那么在上述情况下,如果在 post 方法上声明了抛出检查型异常时,会怎么样?

@PostMapping("/throws")
public ResponseEntity<String> postWithThrows() throws CheckedException {
    return new ResponseEntity<>("success from class controller with throws", HttpStatus.CREATED);
}

此时在 ProxyTest 中进行调用时,可以看到正常捕获到了 CheckedException

这是为什么呢?

在 DefaultGeneratorStrategy.java#L26 处加断点,可以获得到此时的字节码,使用 IDEA 反编译之后,可得到如下内容:

public final ResponseEntity postWithThrows() throws CheckedException {
    try {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? (ResponseEntity)var10000.intercept(this, CGLIB$postWithThrows$1$Method, CGLIB$emptyArgs, CGLIB$postWithThrows$1$Proxy) : super.postWithThrows();
    } catch (Error | CheckedException | RuntimeException var1) {
        throw var1;
    } catch (Throwable var2) {
        throw new UndeclaredThrowableException(var2);
    }
}

可以看到,声明了的异常,在被捕获之后又被抛了出去。其余的 Throwable,会被封装到 UndeclaredThrowableException 中再被抛出。

Puzzle 2

相同情况下,如果代理类是通过 JDK 动态代理创建的,又会是怎么样的呢?

首先,如果希望使用 JDK 动态代理,需要使被代理类实现接口,如 InterfaceOfControllerInterfaceController

@RestController
@RequestMapping("/interface")
public class InterfaceController implements InterfaceOfController {

    @Override
    @PostMapping
    public ResponseEntity post() {
        return new ResponseEntity<>("success from interface controller", HttpStatus.CREATED);
    }

    @Override
    @PostMapping("/throws")
    public ResponseEntity<String> postWithThrows() throws CheckedException {
        return new ResponseEntity<>("success from interface controller with throws", HttpStatus.CREATED);
    }

}

注意:此时需要将 spring web 的注解(@RequestMapping 等)同时写在接口及实现上,否则会找不到对应的接口。

然后在 Spring Boot 应用下,要注意指定 如下参数,否则会强制使用 Cglib:

spring.aop.proxy-target-class=false

可参照 这里 基于接口创建 JDK 动态代理,并输出字节码,观察代理类中相应方法情况。其中关键部分如下:

public final ResponseEntity post() throws  {
    try {
        return (ResponseEntity)super.h.invoke(this, m3, (Object[])null);
    } catch (RuntimeException | Error var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
    }
}
public final ResponseEntity postWithThrows() throws CheckedException {
    try {
        return (ResponseEntity)super.h.invoke(this, m4, (Object[])null);
    } catch (RuntimeException | CheckedException | Error var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
    }
}

可以看到情况与使用 Cglib 时基本一致:

  • 未声明异常时,除 RuntimeExceptionError 外,其余 Throwable 都被封装进了 UndeclaredThrowableException 再抛出;
  • 当声明异常时,声明了的异常也会被直接抛出,不进行封装。
0%