Добрый день!
У меня есть @Controller
. Некоторые из его методов вызывают одно и то же исключение, но я хочу обрабатывать эти исключения по-разному.
Есть ли способ связать @ExceptionHandler
с конкретным методом?
Добрый день!
У меня есть @Controller
. Некоторые из его методов вызывают одно и то же исключение, но я хочу обрабатывать эти исключения по-разному.
Есть ли способ связать @ExceptionHandler
с конкретным методом?
Вам необходимо использовать инструменты АОП, такие как 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.
Как вариант (очевидно, это не идеальный вариант): вы можете заключить исключение в пользовательское исключение в одном из ваших методов и затем перехватить его в @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
}
Я не думаю, что вы можете указать конкретный @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
}
Не могли бы вы объяснить, зачем вам это нужно? Я спрашиваю из любопытства, потому что я никогда не чувствовал, что это требуется, и вот почему:
Исключение обычно представляет собой очень конкретную "ошибку" - что-то, что пошло не так, как надо. По сути, исключение представляет собой ошибку, а не поток...
Есть две "степени свободы", которые пружина может поддерживать из коробки:
Параметры исключения. Может быть, что-то вроде кода ошибки, который может быть объявлен как поле данных самого исключения.
Исключение наследования. Пример:
Если в вашей системе есть UserDoesNotExistException
и вы хотите быть более конкретным в случае, скажем, системы, которая управляет пользователями, которые вышли на пенсию в некоторых потоках, вы всегда можете создать более конкретное исключение:
class UserRetiredException extends UserDoesNotExistException {...}
Очевидно, что Spring может поддерживать оба случая: в ExceptionMapper
вас есть доступ к исключению, так что вы можете сделать что-то вроде:
handleException(SomeExceptionWithErrorCode ex) {
if(ex.getErrorCode() == "A") {
// do this
}
else if(ex.getErrroCode() == "B") {
// do that
}
}
Во втором случае у вас просто есть разные сопоставители исключений для разных типов исключений.
Вы также можете рассмотреть аннотацию @ControllerAdvice
для повторного использования кода или чего-то еще.
Вы можете получить под исключения из общего исключения, сгенерированного другими методами, в зависимости от того, как вы хотите их обработать.
Допустим, вы объявили родительское исключение 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
}
}
Я согласен с тем, что невозможность отобразить определенный @ExceptionHandler для обработки только одного конкретного метода в @RestController должна быть очень желательной функцией.