파란하늘의 지식창고
article thumbnail
반응형

Spring Framework 5.2.3.RELEASE, Spring Boot 2.2.4.RELEASE 기준으로 작성됨

Spring webmvc를 사용하는 경우에 대한 설명


Spring Framework를 쓰면 @ExceptionHandler를 사용하여 전역 에러 처리를 한다. (기존 작성한 글 참조)

2019/04/30 - [Study/Java] - Spring Boot 전역 에러 처리

 

Spring Boot 전역 에러 처리

Spring 5.1.6, Spring Boot 2.1.4 기준 문서 정리 Spring framework는 전역 에러를 처리하기 위해 아래의 인터페이스를 제공한다. 제공되는 interface servlet (webmvc) HandlerExceptionResolver reacitve (webfl..

luvstudy.tistory.com

제공되는 HandlerExceptionResolver로 처리할 수 없는 경우

@ExceptionHandler annotation을 사용하여 다양한 에러들을 처리할 수 있지만 지정된 Exception이 아닌 경우는 처리를 할 수 없다.

예를 들어 Hystrix를 사용하는 경우 HystrixRuntimeException으로 허용된 Exception을 발생시키는데 이때 실제 발생한 Exception은 HystrixRuntimeException의 cause에 담겨 있게 된다. (cause 변수는 Exception의 최상위 객체인 Throwable에 설정된 변수이고 Exception내에 원인이 되는 Exception을 담기 위해 사용된다.)

HystrixRuntimeException의 cause에 AException이 존재하는 경우 처리하는 @ExceptionHandler와 BExcetpion이 존재하는 경우 처리하는 @ExceptionHandler를 구분하여 관리하고 싶어도 스프링이 기본 제공하는 @ExceptionHandler는 선언된 Exception이 아닌 세부적인 조건에 대한 에러 처리를 제공하지 않는다.

이런 경우 별도의 Custom HandlerExceptionResolver를 선언하여 사용해야 한다.

HandlerExceptionResolverComposite의 동작

이전 글에 설명한 바와 같이 Spring에서 @ExceptionHandler annotation을 통한 전역 에러 처리는 ExceptionHandlerExceptionResolver가 담당하고 있다.

Spring webmvc (Servlet 기반 web)를 사용하면 ExceptionHandlerExceptionResolver 외에 DefaultHandlerExceptionResolver, ResponseStatusExceptionResolver가 자동으로 등록된다.

Spring webmvc에서 등록하는 ExceptionResolver 리스트

이 ExceptionResolver들을 순차적으로 실행하여 에러 처리를 하기 위해 HandlerExceptionResolverComposite가 사용된다.

public class HandlerExceptionResolverComposite implements HandlerExceptionResolver, Ordered {

	@Nullable
	private List<HandlerExceptionResolver> resolvers;

	private int order = Ordered.LOWEST_PRECEDENCE;


	/**
	 * Set the list of exception resolvers to delegate to.
	 */
	public void setExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
		this.resolvers = exceptionResolvers;
	}

	/**
	 * Return the list of exception resolvers to delegate to.
	 */
	public List<HandlerExceptionResolver> getExceptionResolvers() {
		return (this.resolvers != null ? Collections.unmodifiableList(this.resolvers) : Collections.emptyList());
	}

	public void setOrder(int order) {
		this.order = order;
	}

	@Override
	public int getOrder() {
		return this.order;
	}


	/**
	 * Resolve the exception by iterating over the list of configured exception resolvers.
	 * <p>The first one to return a {@link ModelAndView} wins. Otherwise {@code null} is returned.
	 */
	@Override
	@Nullable
	public ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

		if (this.resolvers != null) {
			for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
				ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
				if (mav != null) {
					return mav;
				}
			}
		}
		return null;
	}

}

resolver 목록을 순서대로 실행하여 반환된 MovelAndView 값이 null이면 다음 resolver를 실행하는 간단한 composite 패턴이다.

Custom HandlerExceptionResolver 만들기

별도의 Custom HandlerExceptionResolver를 아래와 같이 만든다.

public class HystrixRuntimeAExceptionHandlerExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        // HystrixRuntimeException인지 체크
        if (!(ex instanceof HystrixRuntimeException)) {
            return null;
        }
		
        Throwable cause = ((HystrixRuntimeException) ex).getCause();
        // cause 변수가 A Exception인지 체크
        if (!(cause instanceof AException)) {
            return null;
        }
        AException targetException = (AException) cause;

        // 적당히 반환할 값 처리 (Map이 아니어도 상관없음)
        Map<String, ErrorMessage> resultMap = new HashMap<>();
        resultMap.put("result", "에러 결과 처리");
        ModelAndView modelAndView = new ModelAndView("에러페이지 지정", resultMap);
        modelAndView.setStatus(HttpStatus.BAD_REQUEST);
        return modelAndView; 
    }

}

HystrixRuntimeException의 cause에 AException이 들어있는 경우 해당 에러에 대한 처리를 반환하도록 한 HandlerExceptionResolver이다.

이렇게 만들어진 custom HandlerExceptionResolver를 등록한다.

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
	
    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        HandlerExceptionResolver exceptionHandlerExceptionResolver = resolvers.stream().filter(x -> x instanceof ExceptionHandlerExceptionResolver).findAny().get();
        int index = resolvers.indexOf(exceptionHandlerExceptionResolver);
        resolvers.add(index, new HystrixRuntimeAExceptionHandlerExceptionResolver);
        WebMvcConfigurer.super.extendHandlerExceptionResolvers(resolvers);
    }
		
}

WebMvcConfigurer는 HandlerExceptionResolver를 등록하기 위한 메서드를 2개 제공한다.

configureHandlerExceptionResolvers 메서드는 위에서 설명한 Spring webmvc가 기본 등록하는 3개의 HandlerExceptionResolver 대신 새로 등록 처리를 하는 경우 사용하고 extendHandlerExceptionResolvers 메서드는 기본 등록된 HandlerExceptionResolver에 추가하는 경우 사용한다.

이때 기본 등록된 HandlerExceptionResolver 들보다 우선 동작해야 하기 때문에 위와 같이 처리하였다. (그냥 단순히 0번 index로 add 해도 상관없다.)

 

반응형
profile

파란하늘의 지식창고

@Bluesky_

도움이 되었다면 광고를 클릭해주세요