[spring-mvc] 여러 @ControllerAdvice @ExceptionHandlers의 우선 순위 설정

으로 주석이 달린 다중 클래스가 @ControllerAdvice있으며 각각 @ExceptionHandler메서드가 있습니다.

Exception특정 핸들러가 더 이상 발견되지 않으면이를 사용해야한다는 의도로 처리합니다 .

슬프게도 Spring MVC Exception는 더 구체적인 경우 ( IOException예 :)보다는 항상 가장 일반적인 경우 ( )를 사용하는 것으로 보입니다 .

이것이 Spring MVC가 동작 할 것으로 기대하는 방식입니까? ExceptionMapper처리하는 선언 된 유형이 throw 된 예외에서 얼마나 멀리 떨어져 있는지 확인하고 항상 가장 가까운 조상을 사용하는 각 (동등한 구성 요소)을 평가하는 Jersey에서 패턴을 에뮬레이션하려고합니다 .



답변

이것이 Spring MVC가 동작 할 것으로 기대하는 방식입니까?

Spring 4.3.7부터 Spring MVC가 작동하는 방식은 다음과 같습니다. HandlerExceptionResolver인스턴스를 사용 하여 핸들러 메서드에서 발생한 예외를 처리합니다.

기본적으로 웹 MVC 구성은 하나의 등록 HandlerExceptionResolverbean을하는 HandlerExceptionResolverComposite, 어떤

다른 HandlerExceptionResolvers.

다른 리졸버는

  1. ExceptionHandlerExceptionResolver
  2. ResponseStatusExceptionResolver
  3. DefaultHandlerExceptionResolver

그 순서대로 등록되었습니다. 이 질문의 목적을 위해 우리는 ExceptionHandlerExceptionResolver.

메서드를 AbstractHandlerMethodExceptionResolver통해 예외를 해결 하는 입니다 @ExceptionHandler.

컨텍스트 초기화에서 Spring은 감지 ControllerAdviceBean하는 각 @ControllerAdvice주석이 달린 클래스 에 대해 생성합니다 . 는 ExceptionHandlerExceptionResolver상황에서 이러한를 검색하고이를 사용하여 정렬합니다 AnnotationAwareOrderComparator있는

은 (는) 정적으로 정의 된 주석 값 (있는 경우)을 재정의하는 Ordered 인스턴스에서 제공하는 주문 값과 함께 및 주석
뿐 아니라 OrderComparatorSpring의 Ordered인터페이스 를 지원 하는의 확장입니다 .@Order@Priority

그런 다음 ExceptionHandlerMethodResolverControllerAdviceBean인스턴스 에 대해 를 등록 합니다 (사용 가능한 @ExceptionHandler메서드를 처리하려는 예외 유형에 매핑 ). 마지막으로 동일한 순서로 a에 추가됩니다 LinkedHashMap(반복 순서 유지).

예외가 발생 ExceptionHandlerExceptionResolver하면는 이러한 항목을 반복 ExceptionHandlerMethodResolver하고 예외를 처리 할 수있는 첫 번째 항목을 사용합니다.

여기에 포인트 그래서입니다 : 당신이있는 경우 @ControllerAdvice@ExceptionHandler대한 Exception그 다른 이전에 등록됩니다 @ControllerAdvice와 클래스 @ExceptionHandler보다 구체적인 예외, 등에 IOException, 첫 번째가 호출되는 것이다. 앞서 언급 한 바와 같이, 당신은 당신의 필요에 의해 그 등록 순서를 제어 할 수 @ControllerAdvice주석 클래스가 구현 Ordered또는 그것을 주석 @Order또는 @Priority그것에게 적절한 값을 제공합니다.


답변

Sotirios Delimanolis는 그의 답변에 매우 도움이되었습니다. 추가 조사에서 우리는 어쨌든 봄 3.2.4에서 @ControllerAdvice 주석을 찾는 코드가 @Order 주석의 존재를 확인하고 ControllerAdviceBeans 목록을 정렬한다는 것을 발견했습니다.

@Order 주석이없는 모든 컨트롤러의 결과 기본 순서는 Ordered # LOWEST_PRECEDENCE입니다. 즉, 가장 낮은 우선 순위가 필요한 컨트롤러가 하나 있으면 모든 컨트롤러가 더 높은 순서를 가져야합니다.

다음은 UserProfileException 또는 RuntimeException이 발생할 때 적절한 응답을 제공 할 수있는 ControllerAdvice 및 Order 주석이있는 두 개의 예외 핸들러 클래스를 갖는 방법을 보여주는 예입니다.

class UserProfileException extends RuntimeException {
}

@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
class UserProfileExceptionHandler {
    @ExceptionHandler(UserProfileException)
    @ResponseBody
    ResponseEntity<ErrorResponse> handleUserProfileException() {
        ....
    }
}

@ControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE)
class DefaultExceptionHandler {

    @ExceptionHandler(RuntimeException)
    @ResponseBody
    ResponseEntity<ErrorResponse> handleRuntimeException() {
        ....
    }
}
  • ControllerAdviceBean # initOrderFromBeanType ()을 참조하십시오.
  • ControllerAdviceBean # findAnnotatedBeans ()를 참조하십시오.
  • ExceptionHandlerExceptionResolver # initExceptionHandlerAdviceCache ()를 참조하십시오.

즐겨!


답변

예외 처리기의 순서는 @Order주석을 사용하여 변경할 수 있습니다 .

예를 들면 :

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.ControllerAdvice;

@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomExceptionHandler {

    //...

}

@Order의 값은 임의의 정수일 수 있습니다.


답변

또한 문서에서 다음을 발견했습니다.

https://docs.spring.io/spring-framework/docs/4.3.4.RELEASE/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.html#getExceptionHandlerMethod-org.springframework. web.method.HandlerMethod-java.lang.Exception-

ExceptionHandlerMethod

보호 된 ServletInvocableHandlerMethod getExceptionHandlerMethod (HandlerMethod handlerMethod, 예외 예외)

주어진 예외에 대한 @ExceptionHandler 메소드를 찾으십시오. 기본 구현은 먼저 컨트롤러의 클래스 계층 구조에서 메서드를 검색하고 찾을 수없는 경우 일부 @ControllerAdvice Spring 관리 빈이 감지되었다고 가정하고 추가 @ExceptionHandler 메서드를 계속 검색합니다 . 파라미터 : handlerMethod-예외가 발생한 메서드 (null 일 수 있음) exception-발생한 예외 반환 값 : 예외를 처리하는 메서드 또는 null

따라서이 문제를 해결하려면 해당 예외를 발생시키는 컨트롤러 내에 특정 예외 처리기를 추가해야합니다. ANd는 전역 기본 예외 처리기를 처리하는 유일한 ControllerAdvice를 정의합니다.

이는 프로세스를 단순화하고 문제를 처리하기 위해 주문 주석이 필요하지 않습니다.


답변

유사한 상황 이 Spring 블로그 에있는 우수한 ” Exception Handling in Spring MVC “게시물에있는 Global Exception Handling 섹션에 있습니다. 그들의 시나리오는 예외 클래스에 등록 된 ResponseStatus 주석을 확인하고, 존재하는 경우 예외를 다시 발생시켜 프레임 워크가 처리 할 수 ​​있도록합니다. 이 일반적인 전술을 사용할 수 있습니다. 더 적절한 핸들러가 있는지 확인하고 다시 던질 수 있습니다.

또는 대신 살펴볼 수있는 다른 예외 처리 전략이 있습니다.


답변

처리해야 할 중요한 클래스 :

**@Order(Ordered.HIGHEST_PRECEDENCE)**
public class FunctionalResponseEntityExceptionHandler {
    private final Logger logger = LoggerFactory.getLogger(FunctionalResponseEntityExceptionHandler.class);

    @ExceptionHandler(EntityNotFoundException.class)
    public final ResponseEntity<Object> handleFunctionalExceptions(EntityNotFoundException ex, WebRequest request)
    {
        logger.error(ex.getMessage() + " " + ex);
        ExceptionResponse exceptionResponse= new ExceptionResponse(new Date(), ex.getMessage(),
                request.getDescription(false),HttpStatus.NOT_FOUND.toString());
        return new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
    }
}

우선 순위가 낮은 기타 예외

@ControllerAdvice
    public class GlobalResponseEntityExceptionHandler extends ResponseEntityExceptionHandler
    {
    private final Logger logger = LoggerFactory.getLogger(GlobalResponseEntityExceptionHandler.class);
    @ExceptionHandler(Exception.class)
    public final ResponseEntity<Object> handleAllException(Exception ex, WebRequest request)
    {
        logger.error(ex.getMessage()+ " " + ex);
        ExceptionResponse exceptionResponse= new ExceptionResponse(new Date(), ex.toString(),
                request.getDescription(false),HttpStatus.INTERNAL_SERVER_ERROR.toString());
    }
    }


답변

아래와 같이 숫자 값을 사용할 수도 있습니다.

@Order(value = 100)

값이 낮을수록 우선 순위가 높습니다. 기본값은 * {@code Ordered.LOWEST_PRECEDENCE}이며, 가장 낮은 우선 순위를 나타냅니다 (* 지정된 다른 주문 값에 손실 됨).