Току що обявих новия курс 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