1. 概要

このチュートリアルでは、JavaがUndeclaredThrowableException例外のインスタンスをスローする原因を確認します。

まず、少し理論から始めましょう。 次に、2つの実際の例を使用して、この例外の性質をよりよく理解しようとします。

2. UndeclaredThrowableException

理論的には、宣言されていないチェックされた例外をスローしようとすると、JavaはUndeclaredThrowableExceptionのインスタンスをスローします。 つまり、throws句でチェックされた例外を宣言しませんでしたが、メソッド本体でその例外をスローしました。

Javaコンパイラがコンパイルエラーでこれを防ぐので、これは不可能であると主張する人もいるかもしれません。 たとえば、コンパイルしようとすると、次のようになります。

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

Javaコンパイラは次のメッセージで失敗します。

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

宣言されていないチェック済み例外のスローはコンパイル時に発生しない可能性がありますが、実行時に発生する可能性はあります。 たとえば、例外をスローしないメソッドをインターセプトするランタイムプロキシについて考えてみましょう。

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

プロキシ自体がチェックされた例外をスローした場合、呼び出し元の観点から、saveメソッドはそのチェックされた例外をスローします。 発信者はおそらくそのプロキシについて何も知らず、 保存この例外のために。

このような状況では、Javaは実際にチェックされた例外をUndeclaredThrowableException内にラップし、代わりにUndeclaredThrowableExceptionをスローします。 言及する価値があります UndeclaredThrowableException それ自体はチェックされていない例外です。

理論について十分に理解できたので、実際の例をいくつか見てみましょう。

3. Java動的プロキシ

最初の例として、 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);
    }
}

プロキシされたメソッドが次の場合、このプロキシはチェックされた例外をスローしますサイズ。 それ以外の場合は、チェックされていない例外がスローされます。

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);

上に示したように、 List インターフェースのプロキシを作成し、その上でsizeメソッドを呼び出します。 次に、プロキシは呼び出しをインターセプトし、チェックされた例外をスローします。 次に、Javaはこのチェックされた例外をUndeclaredThrowableExceptionのインスタンス内にラップします。 これは、メソッド宣言で宣言せずに、どういうわけかチェックされた例外をスローするために発生しています。

リストインターフェースで他のメソッドを呼び出す場合:

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

プロキシはチェックされていない例外をスローするため、Javaは例外をそのまま伝播させます。

4. 春の側面

アドバイスされたメソッドがそれらを宣言しなかったときにSpringAspectでチェックされた例外をスローした場合も、同じことが起こります。アノテーションから始めましょう。

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

次に、このアノテーションが付けられたすべてのメソッドにアドバイスします。

@Aspect
@Component
public class UndeclaredAspect {

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

基本的に、このアドバイスは、そのような例外を宣言していなくても、すべての注釈付きメソッドがチェックされた例外をスローするようにします。 それでは、サービスを作成しましょう。

@Service
public class UndeclaredService {

    @ThrowUndeclared
    public void doSomething() {}
}

注釈付きメソッドを呼び出すと、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);
    }
}

上に示したように、Javaは実際の例外を原因としてカプセル化し、代わりにUndeclaredThrowableException例外をスローします。

5. 結論

このチュートリアルでは、JavaがUndeclaredThrowableException例外のインスタンスをスローする原因を確認しました。

いつものように、すべての例はGitHubから入手できます。