우규이인우윀
Eager To Learn 🌌
우규이인우윀
전체 방문자
였늘
μ–΄μ œ

λΈ”λ‘œκ·Έ 메뉴

  • 🏑 ν™ˆ
  • πŸš€ κΉƒν—ˆλΈŒ
  • β›… νƒœκ·Έ ν΄λΌμš°λ“œ
  • λΆ„λ₯˜ 전체보기 (217)
    • πŸ‘¨πŸ»‍πŸ’» PS (170)
      • JAVA (82)
      • MYSQL (1)
      • Docker (2)
      • PYTHON (24)
      • LeetCode 150 (39)
      • Algorithm 기법 (1)
      • 바킹독 (21)
    • λΈ”λ‘œκ·Έ 이사 (0)
    • Error (1)
    • CS (15)
      • DataBase (2)
      • OS (7)
      • Network (1)
      • Spring (1)
      • 자료ꡬ쑰 (3)
      • Java (1)
    • Learned (7)
      • Spring (7)
    • κ°œλ°œμ„œμ  (15)
      • 가상 λ©΄μ ‘ μ‚¬λ‘€λ‘œ λ°°μš°λŠ” λŒ€κ·œλͺ¨ μ‹œμŠ€ν…œ 섀계 기초 (1)
      • 였브젝트 - 쑰영호 (7)
      • μΉœμ ˆν•œ SQL νŠœλ‹ (7)
    • 회고 (2)
hELLO Β· Designed By μ •μƒμš°.
우규이인우윀

Eager To Learn 🌌

Learned/Spring

@ControllerAdvice λŠ” μ–΄λ–»κ²Œ λ™μž‘ν•˜λŠ” κ²ƒμΌκΉŒ?

2023. 7. 27. 17:51

πŸ“Œ μ˜ˆμ™Έ 처리 방식

 

ν”„λ‘œμ νŠΈλ₯Ό μ§„ν–‰ν•˜λ©΄μ„œ, λ‚΄κ°€ 주둜 ν–ˆλ˜ μ˜ˆμ™Έμ²˜λ¦¬ 방법은 λ‹€μŒκ³Ό κ°™λ‹€.

 

Enum νƒ€μž…μ˜ ErrorCode μ •μ˜

@AllArgsConstructor
@Getter
public enum ErrorCode {

    DUPLICATE_USERNAME(HttpStatus.CONFLICT, "이미 μ‘΄μž¬ν•˜λŠ” μ‚¬μš©μžλͺ… μž…λ‹ˆλ‹€.");

    private HttpStatus httpStatus;
    private String message;
}

Http μƒνƒœ μ½”λ“œμ™€ μ—λŸ¬ λ°œμƒ μ‹œ 전달할 λ©”μ„Έμ§€ 정보가 λ‹΄κΈ΄ Enum νƒ€μž…μ˜ ErrorCode λ₯Ό μ •μ˜ν•œλ‹€.

 

RuntimeException 을 μƒμ†ν•˜λŠ” AppException을 μ •μ˜

@Getter
public class AppException extends RuntimeException{
    private ErrorCode errorCode;

    public AppException(ErrorCode errorCode) {
        this.errorCode = errorCode;
    }

}

ErrorCodeλ₯Ό 톡해 μ˜ˆμ™Έ 원인 정보λ₯Ό λ‹΄κ³  μžˆλ‹€.

 

AppException을 μ²˜λ¦¬ν•˜λŠ” Handler μ •μ˜

@RestControllerAdvice
public class ExceptionManager {

    @ExceptionHandler(AppException.class)
    public ResponseEntity<?> appExceptionHandler(AppException e) {

        return ResponseEntity.status(e.getErrorCode().getHttpStatus())
                .body(e.getErrorCode().getMessage());

    }

}

 

μœ„μ™€ 같이 μ •μ˜ν•˜λ©΄, λŸ°νƒ€μž„ μ‹œ AppException이 λ°œμƒν•˜λ©΄ appExceptionHandler λ©”μ„œλ“œκ°€ λ™μž‘ν•œλ‹€.

 

❓ μ΄λ ‡κ²Œ μ˜ˆμ™Έ 처리λ₯Ό μ§„ν–‰ν–ˆμ—ˆλŠ”λ°, μ–΄λ–€ μ›λ¦¬λ‘œ μ΄λ£¨μ–΄μ§€λŠ”μ§€ μ •ν™•νžˆ λͺ¨λ₯΄κ³  μ‚¬μš©ν•œ 것 κ°™μ•„ μ°Ύμ•„λ³΄κΈ°λ‘œ κ²°μ •ν•˜μ˜€λ‹€.

 


πŸ“Œ μ–΄λ…Έν…Œμ΄μ…˜ μ„€λͺ…

 

@RestControllerAdvice

@RestControllerAdviceλŠ” @ControllerAdvice와 @ResponseBody둜 κ΅¬μ„±λ˜μ–΄μžˆλ‹€.

 

@ControllerAdviceλŠ” μ „μ—­μ μœΌλ‘œ μ»¨νŠΈλ‘€λŸ¬μ— 영ν–₯을 λ―ΈμΉ˜λŠ” 역할을 ν•œλ‹€.

 

경둜λ₯Ό λ”°λ‘œ μ§€μ •ν•˜μ§€ μ•ŠμœΌλ©΄, ν”„λ‘œμ νŠΈμ˜ λͺ¨λ“  νŒ¨ν‚€μ§€μ— μžˆμœΌλ©΄μ„œ @Controller μ–΄λ…Έν…Œμ΄μ…˜μ΄ μžˆλŠ” ν΄λž˜μŠ€μ—μ„œ λ°œμƒν•˜λŠ” μ˜ˆμ™Έμƒν™©μ„ μž‘μ„ 수 μžˆλ‹€.

 

그리고 @ControllerAdviceκ°€ 적용된 ν΄λž˜μŠ€μ—λŠ” @ExceptionHandlerλ₯Ό μ μš©ν•œ λ©”μ„œλ“œλ₯Ό μ •μ˜ν•˜κ³  μ²˜λ¦¬ν•  μ˜ˆμ™Έλ₯Ό λͺ…μ‹œν•΄μ£Όλ©΄, λ°œμƒλœ μ˜ˆμ™Έμ™€ μΌμΉ˜ν•˜λŠ” λ©”μ„œλ“œκ°€ μ‹€ν–‰λœλ‹€.

 

πŸ’‘ 보톡 컨트둀러 λ‹¨μ—μ„œ μ„œλΉ„μŠ€ 단을 ν˜ΈμΆœν•˜κ³ , μ„œλΉ„μŠ€ λ‹¨μ—μ„œ λ¬Έμ œκ°€ μžˆλŠ” 경우 RuntimeException μ˜ˆμ™Έλ₯Ό λ˜μ§„λ‹€.

결ꡭ은 μ„œλΉ„μŠ€ 단은 ν˜ΈμΆœν•œ μ»¨νŠΈλ‘€λŸ¬μ— μ˜ˆμ™Έλ₯Ό μ „λ‹¬ν•˜κ³ , 결ꡭ에 μ»¨νŠΈλ‘€λŸ¬μ—μ„œ μ˜ˆμ™Έκ°€ λ°œμƒν•˜λ―€λ‘œ @ControllerAdvice에 μ˜ν•΄ κ°μ§€λ˜λŠ” 것이닀.

 


 

@ExceptionHandler

 

μ •μ˜ν•œ ExceptionManager ν΄λž˜μŠ€μ—μ„œ @ExceptionHandler(AppException.class) 와 같이 μ‚¬μš©ν–ˆλ‹€.

 

μœ„μ—μ„œ μ„€λͺ…ν–ˆλ“―, AppException이 μ»¨νŠΈλ‘€λŸ¬μ—μ„œ κ°μ§€λ˜λ©΄, @ExceptionHandler(AppException.class)κ°€ 적용된 λ©”μ„œλ“œ 둜직이 μ‹€ν–‰λ˜λŠ” 것이닀.

 

 


흐름

좜처 : https://velog.io/@hanblueblue/Spring-ExceptionHandler

잘 μ •λ¦¬λ˜μ–΄ μžˆλŠ” 그림을 λ°œκ²¬ν–ˆλ‹€.

 

μœ„μ™€ 같은 λ°©μ‹μœΌλ‘œ, ν•΄λ‹Ή ν”„λ‘œμ νŠΈ 내에 μžˆλŠ” μ»¨νŠΈλ‘€λŸ¬μ—μ„œ λ°œμƒν•œ μ˜ˆμ™Έλ₯Ό μ–΄λ…Έν…Œμ΄μ…˜μ„ ν†΅ν•΄μ„œ νŠΉμ • λ©”μ„œλ“œλ₯Ό μ‹€ν–‰μ‹œν‚€λ„λ‘ ν•¨μœΌλ‘œμ„œ ν•˜λ‚˜μ˜ 클래슀둜 관리할 수 있게 λ˜λŠ” 것이닀.

 

 


πŸ“Œ DispatcherServlet 을 νŒŒν•΄μ³λ³΄μž

 

 

Dispatcher Servlet은 μ–΄λ–»κ²Œ λ™μž‘ν• κΉŒ?

DispatcherServlet 은 Servlet μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•œ, HTTP ν”„λ‘œν† μ½œλ‘œ λ“€μ–΄μ˜€λŠ” λͺ¨λ“  μš”μ²­μ„ λ¨Όμ € λ°›μ•„ μ²˜λ¦¬ν•˜λŠ” Front Controller 이닀. Servlet μ΄λž€? Java μ–Έμ–΄λ₯Ό 기반으둜, ν΄λΌμ΄μ–ΈνŠΈκ°€ λ³΄λ‚΄λŠ” HTTP μš”μ²­μ„ 처

yinq.tistory.com

 

DispatcherServlet | doDispatch()

 

ν΄λΌμ΄μ–ΈνŠΈμ˜ μš”μ²­μ„ λ°›μ•„ 컨트둀러의 λ©”μ„œλ“œλ₯Ό μ‹€ν–‰ν•˜λŠ” 역할은 DispatcherServlet이 λ‹΄λ‹Ήν•œλ‹€.

 

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ...
    try {
		...
        try {
			...
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
			...
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            dispatchException = new ServletException("Handler dispatch failed: " + err, err);
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    ...
}

doDispatch() λ©”μ„œλ“œλ₯Ό μ‹€ν–‰ν•˜λ©΄

 

ν΄λΌμ΄μ–ΈνŠΈμ˜ μš”μ²­μ„ μ²˜λ¦¬ν•  수 μžˆλŠ” 컨트둀러λ₯Ό μ°Ύκ³ , ν•Έλ“€λŸ¬λ₯Ό μ‹€ν–‰ν•  μ–΄λŽν„°λ₯Ό μ°Ύμ•„ 컨트둀러의 λ©”μ„œλ“œλ₯Ό μ‹€ν–‰ν•œ λ’€ 

 

ModelAndView λΌλŠ” 객체에 κ²°κ³Όλ₯Ό λ³΄κ΄€ν•˜κ³ 

 

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException) λ©”μ„œλ“œλ₯Ό μ‹€ν–‰ν•œλ‹€.

 

κ·Έλ ‡λ‹€λ©΄, 컨트둀러의 λ©”μ„œλ“œλ₯Ό μ‹€ν–‰ν•˜λŠ” 뢀뢄인 

 

mv = ha.handle(processedRequest, response, mappedHandler.getHandler()) λΆ€λΆ„μ—μ„œ μ˜ˆμ™Έκ°€ λ°œμƒν•˜λ©΄

 

try-catch 문에 μ˜ν•΄ dispatchException = ex κ°€ ν• λ‹Ήλ˜μ–΄ processDispatchResult() 문이 싀행될 κ²ƒμœΌλ‘œ μ˜ˆμΈ‘λœλ‹€.

 

 

DispatcherServlet | processDispatchResult()

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

		boolean errorView = false;

		if (exception != null) {
			if (exception instanceof ModelAndViewDefiningException mavDefiningException) {
				logger.debug("ModelAndViewDefiningException encountered", exception);
				mv = mavDefiningException.getModelAndView();
			}
			else {
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}
		...
	}

 

λ©”μ„œλ“œλ₯Ό ν™•μΈν•΄λ³΄λ‹ˆ, μ˜ˆμ™Έ 상황이 λ°œμƒν•˜λ©΄ exception != null μ΄λ―€λ‘œ 쑰건문 μ•ˆμ˜ 둜직이 싀행될 것이닀.

 

μ—¬κΈ°μ„œ λ°œμƒν•œ exception이 ModelAndViewDefiningException 에 μ†ν•˜λŠ” κ²½μš°μ™€ μ•„λ‹Œ 경우둜 λ‚˜λˆ„μ–΄μ§€λŠ”λ°

 

ModelAndViewDefiningException λŠ” Exception 클래슀의 ν•˜μœ„ 클래슀둜, μ˜ˆμ™Έ 상황을 ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ ν™”λ©΄ ν…œν”Œλ¦ΏμœΌλ‘œμ„œ λ³΄μ—¬μ£ΌλŠ” 역할을 ν•œλ‹€.

 

μš°λ¦¬κ°€ μ •μ˜ν•œ μ»€μŠ€ν…€ μ˜ˆμ™ΈλŠ” RuntimeException 을 μƒμ†ν•˜μ—¬ μ •μ˜ν–ˆμœΌλ―€λ‘œ 이 쑰건문에 ν•΄λ‹Ήν•˜μ§€ μ•Šμ„ 것이닀.

 

λ”°λΌμ„œ mv = processHandlerException(request, response, handler, exception) λ©”μ„œλ“œκ°€ 싀행될 것이닀.

 

DispatcherServlet | processHandlerException()

@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
                                               @Nullable Object handler, Exception ex) throws Exception {

	...
    if (this.handlerExceptionResolvers != null) {
        for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
            exMv = resolver.resolveException(request, response, handler, ex);
            if (exMv != null) {
                break;
            }
        }
    }
	...
}

 

μ—¬κΈ°μ„œ HandlerExceptionResolver μΈν„°νŽ˜μ΄μŠ€μ˜ resolveException λ©”μ„œλ“œλ‘œ μ—λŸ¬κ°€ μ²˜λ¦¬λ¨μ„ μ•Œ 수 μžˆλ‹€.

 

AbstractHandlerExceptionResolver | resolveException()

@Override
@Nullable
public ModelAndView resolveException(
        HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

    if (shouldApplyTo(request, handler)) {
        prepareResponse(ex, response);
        ModelAndView result = doResolveException(request, response, handler, ex);
        
     	...
        
        return result;
    }
    else {
        return null;
    }
}

 

결과적으둜 HandlerExceptionResolver의 κ΅¬ν˜„μ²΄ 쀑 ν•˜λ‚˜μΈ AbstractHandlerExceptionResolver의 resolveException() 을 ν˜ΈμΆœν•˜κ³ 

 

ν•΄λ‹Ή ν΄λž˜μŠ€μ—μ„œλŠ” μΆ”μƒλ©”μ„œλ“œλ‘œ μ •μ˜λ˜μ–΄μžˆλŠ”, ν…œν”Œλ¦Ώ λ©”μ„œλ“œ νŒ¨ν„΄μœΌλ‘œ 적용된 doResolveException()을 ν˜ΈμΆœν•œλ‹€.

 

그러면 AbstractHandlerExceptionResolver 클래슀λ₯Ό μƒμ†λ°›λŠ” AbstractHandlerMethodExceptionResolver ν΄λž˜μŠ€μ˜

 

doResolveException()을 ν˜ΈμΆœν•œλ‹€.

 

AbstractHandlerMethodExceptionResolver | doResolveException()

 

@Override
@Nullable
protected final ModelAndView doResolveException(
        HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

    HandlerMethod handlerMethod = (handler instanceof HandlerMethod hm ? hm : null);
    return doResolveHandlerMethodException(request, response, handlerMethod, ex);
}

AbstractHandlerMethodExceptionResolver 클래슀 μ—­μ‹œ, ν…œν”Œλ¦Ώ λ©”μ„œλ“œ νŒ¨ν„΄ ν˜•μ‹μœΌλ‘œ ν•˜μœ„ 클래슀인

 

ExceptionHandlerExceptionResolver 의 doResolveHandlerMethodException() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•œλ‹€.

 

 

ExceptionHandlerExceptionResolver | doResolveHandlerMethodException()

@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
                                                       HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {

    ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
   ...
    try {
       ...
        exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
    }
    catch (Throwable invocationEx) {
      ...
    }
    ...
}

그러면 이 λ©”μ„œλ“œμ—μ„œ getExceptionHandlerMethod() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•œλ‹€.

 

getExceptionHandlerMethod() λ©”μ„œλ“œκ°€ 정말 핡심 뢀뢄이닀.

 

ExceptionHandlerExceptionResolver | getExceptionHandlerMethod()

@Nullable
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
        @Nullable HandlerMethod handlerMethod, Exception exception) {

	...
    
    for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
        ControllerAdviceBean advice = entry.getKey();
        if (advice.isApplicableToBeanType(handlerType)) {
            ExceptionHandlerMethodResolver resolver = entry.getValue();
            Method method = resolver.resolveMethod(exception);
            if (method != null) {
                return new ServletInvocableHandlerMethod(advice.resolveBean(), method, this.applicationContext);
            }
        }
    }

    return null;
}

 

λ“±λ‘λœ λͺ¨λ“  ControllerAdvice λΉˆμ„ κ²€μ‚¬ν•˜κ³  μš°λ¦¬κ°€ doDispatch() μ—μ„œλΆ€ν„° μ „λ‹¬ν–ˆλ˜ exception을 λ°”νƒ•μœΌλ‘œ μ²˜λ¦¬ν•  수 μžˆλŠ” @ExceptionHandler μ–΄λ…Έν…Œμ΄μ…˜κ³Ό μ˜΅μ…˜μœΌλ‘œ μ •μ˜ν•œ μ˜ˆμ™Έλ₯Ό μ²˜λ¦¬ν•  수 μžˆλŠ” λ©”μ„œλ“œλ₯Ό μ°Ύμ•„μ˜¨λ‹€.

 

κ·Έλ ‡κ²Œ μ°Ύμ•„μ˜¨ ExceptionHandler둜 exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments) λ©”μ„œλ“œλ₯Ό 톡해 Reflection API λ₯Ό 톡해 κ΅¬ν˜„ λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜μ—¬ μ˜ˆμ™Έμ²˜λ¦¬λ₯Ό ν•˜κ²Œ λœλ‹€.

 

 


μ°Έκ³ ν•œ λΈ”λ‘œκ·Έ : https://mangkyu.tistory.com/24
    'Learned/Spring' μΉ΄ν…Œκ³ λ¦¬μ˜ λ‹€λ₯Έ κΈ€
    • Dispatcher Servlet은 μ–΄λ–»κ²Œ λ™μž‘ν• κΉŒ?
    • Swagger API 적용 μ‹œ, Controller μ½”λ“œκ°€ λ„ˆλ¬΄ λ”λŸ¬μ›Œμ§„λ‹€.. λΆ„λ¦¬ν•΄λ³΄μž
    • Dto Validation μ˜ˆμ™Έ μ²˜λ¦¬μ— AOPλ₯Ό μ μš©ν•΄λ³΄μž!
    • ν…ŒμŠ€νŠΈ μ½”λ“œ 컀버리지 뢄석 도ꡬ Jacocoλ₯Ό μ μš©ν•΄λ³΄μž!
    우규이인우윀
    우규이인우윀
    개발자 κΏˆλ‚˜λ¬΄

    ν‹°μŠ€ν† λ¦¬νˆ΄λ°”