Кога Java хвърля UndeclaredThrowableException?

Java Top

Току що обявих новия курс Learn Spring , фокусиран върху основите на Spring 5 и Spring Boot 2:

>> ПРЕГЛЕД НА КУРСА

1. Общ преглед

В този урок ще видим какво кара Java да хвърли екземпляр на изключението UndeclaredThrowableException .

Първо, ще започнем с малко теория. След това ще се опитаме да разберем по-добре същността на това изключение с два реални примера.

2. UndeclaredThrowableException

Теоретично погледнато, Java ще хвърли екземпляр на UndeclaredThrowableException, когато се опитаме да хвърлим недекларирано проверено изключение. Това означава, че не декларирахме провереното изключение в клаузата за хвърляне , но хвърляме това изключение в тялото на метода.

Може да се твърди, че това е невъзможно, тъй като компилаторът на 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 ще обгърне действителното проверено изключение в 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 proxy = (List) Proxy.newProxyInstance(classLoader, new Class[] { List.class }, invocationHandler); assertThatThrownBy(proxy::size) .isInstanceOf(UndeclaredThrowableException.class) .hasCauseInstanceOf(SomeCheckedException.class);

Както е показано по-горе, ние създаваме прокси за интерфейса на List и извикваме метода за размер върху него. Проксито от своя страна прихваща повикването и хвърля отметнато изключение. След това Java обвива това проверено изключение в екземпляр на UndeclaredThrowableException.Това се случва, защото по някакъв начин хвърляме проверено изключение, без да го декларираме в декларацията на метода.

Ако извикаме друг метод в интерфейса на List :

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

Тъй като проксито изхвърля непроверено изключение, Java позволява на изключението да се разпространява както е.

4. Пролетен аспект

Същото се случва, когато хвърляме проверено изключение в Spring Aspect, докато препоръчаните методи не ги декларират. Нека започнем с анотация:

@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.

Дъно на Java

Току що обявих новия курс Learn Spring , фокусиран върху основите на Spring 5 и Spring Boot 2:

>> ПРЕГЛЕД НА КУРСА