Quand Java lance-t-il une exception UndeclaredThrowableException?

Haut Java

Je viens d'annoncer le nouveau cours Learn Spring , axé sur les principes de base de Spring 5 et Spring Boot 2:

>> VOIR LE COURS

1. Vue d'ensemble

Dans ce didacticiel, nous allons voir ce qui pousse Java à lancer une instance de l' exception UndeclaredThrowableException .

Tout d'abord, nous allons commencer par un peu de théorie. Ensuite, nous essaierons de mieux comprendre la nature de cette exception avec deux exemples concrets.

2. L' exception UndeclaredThrowableException

Théoriquement parlant, Java lancera une instance de UndeclaredThrowableException lorsque nous essayons de lancer une exception vérifiée non déclarée. Autrement dit, nous n'avons pas déclaré l'exception vérifiée dans la clause throws , mais nous lançons cette exception dans le corps de la méthode.

On pourrait dire que c'est impossible car le compilateur Java empêche cela avec une erreur de compilation. Par exemple, si nous essayons de compiler:

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

Le compilateur Java échoue avec le message:

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

Même si lever des exceptions vérifiées non déclarées peut ne pas se produire au moment de la compilation, c'est toujours une possibilité au moment de l'exécution. Par exemple, considérons un proxy d'exécution interceptant une méthode qui ne lève aucune exception:

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

Si le proxy lui-même lève une exception vérifiée, du point de vue de l'appelant, la méthode save lève cette exception vérifiée. L'appelant ne sait probablement rien de ce proxy et blâmera la sauvegarde pour cette exception.

Dans de telles circonstances, Java encapsulera l'exception vérifiée réelle dans une UndeclaredThrowableException et lèvera à la place l' exception UndeclaredThrowableException . Il convient de mentionner que l' exception UndeclaredThrowableException elle-même est une exception non vérifiée.

Maintenant que nous en savons suffisamment sur la théorie, voyons quelques exemples concrets.

3. Proxy dynamique Java

Comme premier exemple, créons un proxy d'exécution pour l' interface java.util.List et interceptons ses appels de méthode. Tout d'abord, nous devons implémenter l' interface InvocationHandler et y mettre la logique supplémentaire:

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

Ce proxy lève une exception vérifiée si la méthode proxy est size. Sinon, cela lèvera une exception non vérifiée.

Voyons comment Java gère les deux situations. Tout d'abord, nous allons appeler la méthode List.size () :

ClassLoader classLoader = getClass().getClassLoader(); InvocationHandler invocationHandler = new ExceptionalInvocationHandler(); List proxy = (List) Proxy.newProxyInstance(classLoader, new Class[] { List.class }, invocationHandler); assertThatThrownBy(proxy::size) .isInstanceOf(UndeclaredThrowableException.class) .hasCauseInstanceOf(SomeCheckedException.class);

Comme indiqué ci-dessus, nous créons un proxy pour l' interface List et appelons la méthode de taille dessus. Le proxy, à son tour, intercepte l'appel et lève une exception vérifiée. Ensuite, Java encapsule cette exception vérifiée dans une instance de UndeclaredThrowableException.Cela se produit parce que nous lançons en quelque sorte une exception vérifiée sans la déclarer dans la déclaration de méthode.

Si nous appelons une autre méthode sur l' interface List :

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

Étant donné que le proxy lève une exception non contrôlée, Java laisse l'exception se propager telle quelle.

4. Aspect du ressort

La même chose se produit lorsque nous lançons une exception vérifiée dans un Spring Aspect alors que les méthodes conseillées ne les déclaraient pas. Commençons par une annotation:

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

Nous allons maintenant conseiller toutes les méthodes annotées avec cette annotation:

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

Fondamentalement, ce conseil obligera toutes les méthodes annotées à lancer une exception vérifiée, même si elles n'ont pas déclaré une telle exception . Maintenant, créons un service:

@Service public class UndeclaredService { @ThrowUndeclared public void doSomething() {} }

Si nous appelons la méthode annotée, Java lancera une instance d' exception 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); } }

Comme indiqué ci-dessus, Java encapsule l'exception réelle en tant que cause et lève l' exception UndeclaredThrowableException à la place.

5. Conclusion

Dans ce didacticiel, nous avons vu ce qui pousse Java à lancer une instance de l' exception UndeclaredThrowableException .

Comme d'habitude, tous les exemples sont disponibles sur over sur GitHub.

Fond Java

Je viens d'annoncer le nouveau cours Learn Spring , axé sur les principes de base de Spring 5 et Spring Boot 2:

>> VOIR LE COURS