知識庫 / Spring RSS 訂閱

當 Java 拋出 UndeclaredThrowableException 異常時?

Java,Spring
HongKong
4
12:52 PM · Dec 06 ,2025

1. 概述

本教程將探討導致 Java 拋出 UndeclaredThrowableException 異常的原因。

首先,我們將從一些理論知識入手。然後,我們將通過兩個實際案例,更好地理解該異常的本質。

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
}

如果代理本身拋出受檢異常,從調用者的角度來看,保存方法會拋出該受檢異常。 調用者可能對該代理一無所知,並將該異常歸咎於 保存方法。

在這種情況下,Java會將實際的受檢異常包裝在 未聲明異常中,並拋出 未聲明異常 值得注意的是,未聲明異常本身是一個未檢查異常。

現在我們對理論有了足夠的瞭解,讓我們看看一些實際的例子。

3. Java 動態代理

作為我們第一個示例,讓我們為 java.util.List 接口創建一個運行時代理,並攔截其方法調用。首先,我們應該實現 <a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/reflect/InvocationHandler.html">InvocationHandler</a> 接口,並將額外的邏輯放在其中:

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

此代理在代理的方法是<em>size</em>時,會拋出一個檢查式異常。否則,它將拋出一個未檢查式異常。

讓我們看看Java如何處理兩種情況。首先,我們將調用<em>List.size()</em>方法:

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 實例中。 這之所以發生,是因為我們以某種方式在不聲明該方法聲明中拋出檢查異常。 如果我們在 List 接口上調用任何其他方法:

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

由於代理拋出未檢查異常,Java 允許異常原樣傳播。

4. Spring 方面(Aspect)

當我們在 Spring 方面(Aspect)中拋出檢查異常(checked exception),且被建議的方法(advised methods)未聲明它們時,同樣會發生同樣的情況。 讓我們從一個註解開始:

@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) 異常的原因。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.