When Does Java Throw UndeclaredThrowableException? – Java什么时候会抛出未声明的可抛出异常?

最后修改: 2020年 7月 3日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’re going to see what causes Java to throw an instance of the UndeclaredThrowableException exception.

在本教程中,我们要看看是什么原因导致Java抛出一个UndeclaredThrowableException异常实例。

First, we’ll start with a bit of theory. Then, we’ll try to better understand the nature of this exception with two real-world examples.

首先,我们将从一点理论开始。然后,我们将尝试通过两个真实世界的例子来更好地理解这种例外的性质。

2. The UndeclaredThrowableException

2.未声明的可抛出异常

Theoretically speaking, Java will throw an instance of UndeclaredThrowableException when we try to throw an undeclared checked exception. That is, we didn’t declare the checked exception in the throws clause but we throw that exception in the method body.

从理论上讲,当我们试图抛出一个未声明的检查的异常时,Java将抛出一个UndeclaredThrowableException实例。也就是说,我们没有在throws clause中声明检查过的异常,但是我们在方法体中抛出了该异常。

One might argue that this is impossible as the Java compiler prevents this with a compilation error. For instance, if we try to compile:

有人可能会说,这是不可能的,因为Java编译器会以编译错误的方式来阻止这一点。例如,如果我们试图编译。

public void undeclared() {
    throw new IOException();
}

The Java compiler fails with the message:

Java编译器失败,出现了这样的信息。

java: unreported exception java.io.IOException; must be caught or declared to be thrown

Even though throwing undeclared checked exceptions may not happen at compile-time, it’s still a possibility at runtime. For example, let’s consider a runtime proxy intercepting a method that doesn’t throw any exceptions:

即使抛出未声明的检查异常在编译时可能不会发生,但在运行时仍有可能发生。例如,让我们考虑一个运行时代理拦截一个不抛出任何异常的方法。

public void save(Object data) {
    // omitted
}

If the proxy itself throws a checked exception, from the caller’s perspective, the save method throws that checked exception. The caller probably doesn’t know anything about that proxy and will blame the save for this exception.

如果代理本身抛出了一个被检查的异常,从调用者的角度来看,save 方法抛出了这个被检查的异常。调用者可能对该代理一无所知,并将这一异常归咎于save

In such circumstances, Java will wrap the actual checked exception inside an UndeclaredThrowableException and throw the UndeclaredThrowableException instead. It’s worth mentioning that the UndeclaredThrowableException itself is an unchecked exception.

在这种情况下,Java将把实际的检查过的异常包裹在一个UndeclaredThrowableException中,并抛出UndeclaredThrowableException代替。值得一提的是,UndeclaredThrowableException本身就是一个未检查的异常。

Now that we know enough about the theory, let’s see a few real-world examples.

现在我们对理论有了足够的了解,让我们看看几个真实世界的例子。

3. Java Dynamic Proxy

3.Java动态代理

As our first example, let’s create a runtime proxy for java.util.List interface and intercept its method calls. First, we should implement the InvocationHandler interface and put the extra logic there:

作为我们的第一个例子,让我们为java.util.List 接口创建一个运行时代理并拦截其方法调用。首先,我们应该实现InvocationHandler接口,并将额外的逻辑放在那里。

public class ExceptionalInvocationHandler implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("size".equals(method.getName())) {
            throw new SomeCheckedException("Always fails");
        }
            
        throw new RuntimeException();
    }
}

public class SomeCheckedException extends Exception {

    public SomeCheckedException(String message) {
        super(message);
    }
}

This proxy throws a checked exception if the proxied method is size. Otherwise, it’ll throw an unchecked exception.

如果被代理的方法是size,这个代理会抛出一个检查过的异常。否则,它将抛出一个未检查的异常。

Let’s see how Java handles both situations. First, we’ll call the List.size() method:

让我们看看Java如何处理这两种情况。首先,我们将调用List.size() 方法。

ClassLoader classLoader = getClass().getClassLoader();
InvocationHandler invocationHandler = new ExceptionalInvocationHandler();
List<String> proxy = (List<String>) Proxy.newProxyInstance(classLoader, 
  new Class[] { List.class }, invocationHandler);

assertThatThrownBy(proxy::size)
  .isInstanceOf(UndeclaredThrowableException.class)
  .hasCauseInstanceOf(SomeCheckedException.class);

As shown above, we create a proxy for the List interface and call the size method on it. The proxy, in turn, intercepts the call and throws a checked exception. Then, Java wraps this checked exception inside an instance of UndeclaredThrowableException. This is happening because we somehow throw a checked exception without declaring it in the method declaration.

如上所示,我们为List接口创建了一个代理,并对其调用size方法。该代理拦截了该调用,并抛出了一个检查异常。然后,Java将这个检查过的异常包裹在UndeclaredThrowableException的实例中。发生这种情况是因为我们在方法声明中没有声明就抛出了一个检查过的异常。

If we call any other method on the List interface:

如果我们在List 界面上调用任何其他方法。

assertThatThrownBy(proxy::isEmpty).isInstanceOf(RuntimeException.class);

Since the proxy throws an unchecked exception, Java lets the exception to propagate as-is.

由于代理抛出的是一个未检查的异常,Java让异常按原样传播。

4. Spring Aspect

4.Spring方面

The same thing happens when we throw a checked exception in a Spring Aspect while the advised methods didn’t declare them. Let’s start with an annotation:

当我们在Spring Aspect中抛出一个检查过的异常,而建议的方法没有声明它们时,同样的事情发生了。让我们从一个注解开始。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ThrowUndeclared {}

Now we’re going to advise all methods annotated with this annotation:

现在我们要建议所有用这个注解的方法。

@Aspect
@Component
public class UndeclaredAspect {

    @Around("@annotation(undeclared)")
    public Object advise(ProceedingJoinPoint pjp, ThrowUndeclared undeclared) throws Throwable {
        throw new SomeCheckedException("AOP Checked Exception");
    }
}

Basically, this advice will make all annotated methods to throw a checked exception, even if they didn’t declare such an exception. Now, let’s create a service:

基本上,这个建议将使所有被注解的方法抛出一个被检查的异常,即使它们没有声明这样的异常。现在,让我们创建一个服务。

@Service
public class UndeclaredService {

    @ThrowUndeclared
    public void doSomething() {}
}

If we call the annotated method, Java will throw an instance of UndeclaredThrowableException exception:

如果我们调用注释的方法,Java将抛出一个UndeclaredThrowableException异常的实例。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = UndeclaredApplication.class)
public class UndeclaredThrowableExceptionIntegrationTest {

    @Autowired private UndeclaredService service;

    @Test
    public void givenAnAspect_whenCallingAdvisedMethod_thenShouldWrapTheException() {
        assertThatThrownBy(service::doSomething)
          .isInstanceOf(UndeclaredThrowableException.class)
          .hasCauseInstanceOf(SomeCheckedException.class);
    }
}

As shown above, Java encapsulates the actual exception as a cause and throws the UndeclaredThrowableException exception instead.

如上所示,Java将实际的异常封装为一个原因,并抛出UndeclaredThrowableException异常来代替。

5. Conclusion

5.总结

In this tutorial, we saw what causes Java to throw an instance of the UndeclaredThrowableException exception.

在本教程中,我们看到了导致Java抛出一个UndeclaredThrowableException异常实例的原因。

As usual, all the examples are available over on GitHub.

像往常一样,所有的例子都可以在GitHub上找到