Spring MVC: привязывает обработчик исключений к определенному методу

Добрый день!

У меня есть @Controller. Некоторые из его методов вызывают одно и то же исключение, но я хочу обрабатывать эти исключения по-разному.

Есть ли способ связать @ExceptionHandler с конкретным методом?

Ответ 1

Вам необходимо использовать инструменты АОП, такие как CDI Interceptor или AspectJ, для решения этой сквозной задачи. Концерн - это термин, который относится к части системы, разделенной по функциональности.

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

Например, если вы хотите изменить регистратор для приложения с log4j на sl4j, вам нужно пройти через все классы, где вы использовали log4j, и изменить его. Но если вы использовали инструменты AOP, вам нужно только перейти к классу перехватчиков и изменить реализацию. Что-то вроде подключи и играй и очень мощный инструмент.

Вот фрагмент кода с использованием JavaEE CDI Interceptor

/*
    Creating the interceptor binding
*/
@InterceptorBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface BindException {

}

После того, как мы определим привязку перехватчика, нам нужно определить реализацию привязки перехватчика

/*
    Creating the interceptor implementation
*/
@Interceptor
@BindException
public class ExceptionCDIInterceptor {

    @AroundInvoke
    public Object methodInterceptor(InvocationContext ctx) throws Exception {
        System.out.println("Invoked method " + ctx.getMethod().getName());
        try {
            return ctx.proceed(); // this line will try to execute your method
                                 // and if the method throw the exception it will be caught  
        } catch (Exception ex) {
            // here you can check for your expected exception 
            // code for Exception handler
        }
    }

}

Теперь нам нужно только применить перехватчик к нашему методу

/*
    Some Service class where you want to implement the interceptor
*/
@ApplicationScoped
public class Service {

    // adding annotation to thisMethodIsBound method to intercept
    @BindException
    public String thisMethodIsBound(String uid) {
        // codes....

        // if this block throw some exception then it will be handled by try catch block
        // from ExceptionCDIInterceptor
    }
}

Вы можете достичь той же функции, используя AspectJ также.

/*
    Creating the Aspect implementation
*/
@Aspect
public class  ExceptionAspectInterceptor {

    @Around("execution(* com.package.name.SomeService.thisMethodIsBound.*(..))")
    public Object methodInterceptor(ProceedingJoinPoint ctx) throws Throwable {
        System.out.println("Invoked method " + ctx.getSignature().getName());
        try {
            return ctx.proceed(); // this line will try to execute your method
                                 // and if the method throw the exception it will be caught  
        } catch (Exception ex) {
            // here you can check for your expected exception 
            // codes for Exception handler
        }
    }
}

Теперь нам нужно только включить AspectJ в конфигурацию нашего приложения

/*
    Enable the AspectJ in your application
*/
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

    @Bean
    public SomeService SomeService() {
        return new SomeService();
    }

}

/*
    Some Service class where you want to implement the Aspect
*/
package com.package.name;
public class SomeService {

    public String thisMethodIsBound(String uid) {
        // codes....

        // if this block throw some exception then it will be handled by try catch block
        // from ExceptionAspectInterceptor
    }
}

У меня есть пример кода в моем репозитории git https://github.com/prameshbhattarai/javaee-exceptionBinding с использованием перехватчика CDI.

Ответ 2

Как вариант (очевидно, это не идеальный вариант): вы можете заключить исключение в пользовательское исключение в одном из ваших методов и затем перехватить его в @ExceptionHandler

void boo() throws WrappingException {
    try {

    } catch (TargetException e) {
        throw new WrappingException(e);
    }
}

затем

@ExceptionHandler(WrappingException.class)
public void handleWrappingException() {
    // handle
}

@ExceptionHandler(TargetException.class)
public void handleTargetException() {
    // handle
}

Ответ 3

Я не думаю, что вы можете указать конкретный @ExceptionHandler для метода, но вы можете привязать метод @ExceptionHandler к определенному Exception.

Поэтому, если вы хотите обработать все DataIntegrityViolationException одну сторону и все другие Исключения в другом, вы сможете добиться этого с помощью чего-то вроде этого:

@ExceptionHandler(DataIntegrityViolationException.class)
public void handleIntegrityViolation() {
    // do stuff for integrity violation here
}

@ExceptionHandler(Exception.class)
public void handleEverythingElse() {
    // do stuff for everything else here
}

Ответ 4

Не могли бы вы объяснить, зачем вам это нужно? Я спрашиваю из любопытства, потому что я никогда не чувствовал, что это требуется, и вот почему:

Исключение обычно представляет собой очень конкретную "ошибку" - что-то, что пошло не так, как надо. По сути, исключение представляет собой ошибку, а не поток...

Есть две "степени свободы", которые пружина может поддерживать из коробки:

  1. Параметры исключения. Может быть, что-то вроде кода ошибки, который может быть объявлен как поле данных самого исключения.

  2. Исключение наследования. Пример:

Если в вашей системе есть UserDoesNotExistException и вы хотите быть более конкретным в случае, скажем, системы, которая управляет пользователями, которые вышли на пенсию в некоторых потоках, вы всегда можете создать более конкретное исключение:

class UserRetiredException extends UserDoesNotExistException {...}

Очевидно, что Spring может поддерживать оба случая: в ExceptionMapper вас есть доступ к исключению, так что вы можете сделать что-то вроде:

handleException(SomeExceptionWithErrorCode ex) {
   if(ex.getErrorCode() == "A") {
      // do this
   }
   else if(ex.getErrroCode() == "B") {
      // do that
   }
}

Во втором случае у вас просто есть разные сопоставители исключений для разных типов исключений.

Вы также можете рассмотреть аннотацию @ControllerAdvice для повторного использования кода или чего-то еще.

Ответ 5

Вы можете получить под исключения из общего исключения, сгенерированного другими методами, в зависимости от того, как вы хотите их обработать.

Допустим, вы объявили родительское исключение ParentException. ChildAException extends ParentException, ChildBException extends ParentException как ChildAException extends ParentException, ChildBException extends ParentException и т.д.

Определите класс @ControllerAdvice который перехватывает ParentException и определите специфическое поведение в методах делегатов.

@ControllerAdvice
public class ParentExceptionHandler {

    @ExceptionHandler(ParentException.class)
    public ResponseEntity<Object> handleParentException(ParentException pe) {
        if (pe instanceof ChildAException) {
            return handleChildAException((ChildAException) pe);
        } else if (...) {
            ...
        } else {
            // handle parent exception
        }
    }

    private ResponseEntity<Object> handleChildAException(ChildAException cae) {
        // handle child A exception
    }
}

Ответ 6

Я согласен с тем, что невозможность отобразить определенный @ExceptionHandler для обработки только одного конкретного метода в @RestController должна быть очень желательной функцией.