美文网首页
谁动了我的异常 Plus

谁动了我的异常 Plus

作者: AlphaHinex | 来源:发表于2020-07-19 10:27 被阅读0次

    原文地址:https://alphahinex.github.io/2020/07/19/undeclared-throwable-exception-plus/

    cover

    description: "《谁动了我的异常?》中的未解之谜"
    date: 2020.07.19 19:34
    categories:
    - Spring
    tags: [Java, Spring]
    keywords: Java, Spring, AOP, Proxy, CGLIB, JDK Dynamic Proxy, UndeclaredThrowableException, Checked Exception, Unchecked Exception


    书接上文,在 谁动了我的异常? 中,有几个问题没说清楚,本文将继续进行说明。
    为了方便,相关示例代码上传至了 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 再抛出;
    • 当声明异常时,声明了的异常也会被直接抛出,不进行封装。

    相关文章

      网友评论

          本文标题:谁动了我的异常 Plus

          本文链接:https://www.haomeiwen.com/subject/bpaqkktx.html