Рубрики
Без рубрики

Когда Java Создает исключение UndeclaredThrowableException?

Узнайте, что заставляет Java выдавать исключение UndeclaredThrowableException

Автор оригинала: Mona Mohamadinia.

1. Обзор

В этом уроке мы рассмотрим, что заставляет Java выбрасывать экземпляр UndeclaredThrowableException exception.

Во-первых, мы начнем с небольшой теории. Затем мы попытаемся лучше понять природу этого исключения с помощью двух реальных примеров.

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 создает это проверенное исключение. Вызывающий, вероятно, ничего не знает об этом прокси-сервере и будет обвинять save в этом исключении.

В таких обстоятельствах Java обернет фактическое проверенное исключение в исключение UndeclaredThrowableException и вместо этого выбросит исключение UndeclaredThrowableException . Стоит отметить, что UndeclaredThrowableException само по себе является непроверенным исключением.

Теперь, когда мы достаточно знаем об этой теории, давайте рассмотрим несколько реальных примеров.

3. Динамический прокси-сервер Java

В качестве нашего первого примера давайте создадим runtime proxy для java.util.Перечислите интерфейс и перехватите вызовы его методов. Во-первых, мы должны реализовать интерфейс 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 и вызываем на нем метод size . Прокси, в свою очередь, перехватывает вызов и выдает проверенное исключение. Затем Java обертывает это проверенное исключение внутри экземпляра UndeclaredThrowableException. Это происходит потому, что мы каким-то образом выбрасываем проверенное исключение, не объявляя его в объявлении метода.

Если мы вызовем любой другой метод в интерфейсе List :

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

Поскольку прокси-сервер выдает непроверенное исключение, Java позволяет исключению распространяться как есть.

4. Пружинный аспект

То же самое происходит, когда мы выбрасываем проверенное исключение в аспекте Spring , в то время как рекомендуемые методы их не объявляли. Давайте начнем с аннотации:

@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 exception:

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

Как обычно, все примеры доступны на GitHub .