πŸ“–
Aaron's TECH BOOK
  • Intro
    • About me
  • Lecture
    • Kubernetes
      • Begin Kubernetes
    • Kafka
      • Begin Kafka
    • Kotlin
      • TDD, Clean Code Preview
      • woowa Kotlin
    • Java
      • Multithread Concurrency
      • The Java
    • Toby
      • Toby Spring 6
      • Toby Spring Boot
    • MSA
      • 01.Micro Service
      • 02.DDD 섀계
      • 03.DDD κ΅¬ν˜„
      • 04.EDA κ΅¬ν˜„
    • Spring Boot
    • Spring Batch
    • Spring Core Advanced
    • Spring DB Part II
    • Spring DB Part I
    • JPA API and Performance Optimization
    • JPA Web Application
    • JPA Programming Basic
    • Spring MVC Part 2
      • 01.Thymeleaf
      • 02.ETC
      • 03.Validation
      • 04.Login
      • 05.Exception
    • Spring MVC Part 1
      • 01.Servlet
      • 02.MVC
    • Http
      • 01.Basic
      • 02.Method
      • 03.Header
    • Spring Core
    • Study
      • Concurrency issues
      • First Come First Served
      • Performance Test
      • TDD
      • IntelliJ
  • Book
    • Kafka Streams in Action
      • 01.μΉ΄ν”„μΉ΄ 슀트림즈
      • 02.μΉ΄ν”„μΉ΄ 슀트림즈 개발
      • 03.μΉ΄ν”„μΉ΄ 슀트림즈 관리
    • Effective Kotlin
      • 01.쒋은 μ½”λ“œ
      • 02.μ½”λ“œ 섀계
      • 03.νš¨μœ¨μ„±
    • 이벀트 μ†Œμ‹±κ³Ό MSA
      • 01.도메인 주도 섀계
      • 02.객체지ν–₯ 섀계 원칙
      • 03-04.이벀트 μ†Œμ‹±
      • 05.λ§ˆμ΄ν¬λ‘œμ„œλΉ„μŠ€ ν˜‘μ—…
      • 06.결과적 일관성
      • 07.CQRS
      • 08.UI
      • 09.ν΄λΌμš°λ“œ ν™˜κ²½
    • λͺ½κ³ DB μ™„λ²½ κ°€μ΄λ“œ
      • I. λͺ½κ³ DB μ‹œμž‘
      • II. λͺ½κ³ DB 개발
    • Kotlin Cookbook
      • μ½”ν‹€λ¦° 기초
      • μ½”ν‹€λ¦° κΈ°λŠ₯
      • ETC
    • Kotlin in Action
      • ν•¨μˆ˜/클래슀/객체/μΈν„°νŽ˜μ΄μŠ€
      • λžŒλ‹€μ™€ νƒ€μž…
      • μ˜€λ²„λ‘œλ”©κ³Ό κ³ μ°¨ ν•¨μˆ˜
      • μ œλ„€λ¦­μŠ€, μ• λ…Έν…Œμ΄μ…˜, λ¦¬ν”Œλ ‰μ…˜
    • Kent Beck Tidy First?
    • λŒ€κ·œλͺ¨ μ‹œμŠ€ν…œ 섀계 기초
      • 01.μ‚¬μš©μž μˆ˜μ— λ”°λ₯Έ 규λͺ¨ ν™•μž₯μ„±
      • 02.개랡적인 규λͺ¨ μΆ”μ •
      • 03.μ‹œμŠ€ν…œ 섀계 κ³΅λž΅λ²•
      • 04.처리율 μ œν•œ μž₯치 섀계
      • 05.μ•ˆμ • ν•΄μ‹œ 섀계
      • 06.ν‚€-κ°’ μ €μž₯μ†Œ 섀계
      • 07.유일 ID 생성기 섀계
      • 08.URL 단좕기 섀계
      • 09.μ›Ή 크둀러 섀계
      • 10.μ•Œλ¦Ό μ‹œμŠ€ν…œ 섀계
      • 11.λ‰΄μŠ€ ν”Όλ“œ μ‹œμŠ€ν…œ 섀계
      • 12.μ±„νŒ… μ‹œμŠ€ν…œ 섀계
      • 13.검색어 μžλ™μ™„μ„± μ‹œμŠ€ν…œ
      • 14.유튜브 섀계
      • 15.ꡬ글 λ“œλΌμ΄λΈŒ 섀계
      • 16.배움은 κ³„μ†λœλ‹€
    • μ‹€μš©μ£Όμ˜ ν”„λ‘œκ·Έλž˜λ¨ΈπŸ“–
    • GoF Design Patterns
    • 도메인 주도 개발 μ‹œμž‘ν•˜κΈ°
      • 01.도메인 λͺ¨λΈ μ‹œμž‘ν•˜κΈ°
      • 02.μ•„ν‚€ν…μ²˜ κ°œμš”
      • 03.μ• κ·Έλ¦¬κ±°νŠΈ
      • 04.리포지터리와 λͺ¨λΈ κ΅¬ν˜„
      • 05.Spring Data JPAλ₯Ό μ΄μš©ν•œ 쑰회 κΈ°λŠ₯
      • 06.μ‘μš© μ„œλΉ„μŠ€μ™€ ν‘œν˜„ μ˜μ—­
      • 07.도메인 μ„œλΉ„μŠ€
      • 08.μ• κ·Έλ¦¬κ±°νŠΈ νŠΈλžœμž­μ…˜ 관리
      • 09.도메인 λͺ¨λΈκ³Ό λ°”μš΄λ””λ“œ μ»¨ν…μŠ€νŠΈ
      • 10.이벀트
      • 11.CQRS
    • Effective Java 3/E
      • 객체, 곡톡 λ©”μ„œλ“œ
      • 클래슀, μΈν„°νŽ˜μ΄μŠ€, μ œλ„€λ¦­
    • μ†Œν”„νŠΈμ›¨μ–΄ μž₯인
    • ν•¨κ»˜ 자라기
    • Modern Java In Action
      • 01.기초
      • 02.ν•¨μˆ˜ν˜• 데이터 처리
      • 03.슀트림과 λžŒλ‹€λ₯Ό μ΄μš©ν•œ 효과적 ν”„λ‘œκ·Έλž˜λ°
      • 04.맀일 μžλ°”μ™€ ν•¨κ»˜
    • Refactoring
      • 01.λ¦¬νŽ™ν„°λ§ 첫 번째 μ˜ˆμ‹œ
      • 02.λ¦¬νŽ™ν„°λ§ 원칙
      • 03.μ½”λ“œμ—μ„œ λ‚˜λŠ” μ•…μ·¨
      • 06.기본적인 λ¦¬νŽ™ν„°λ§
      • 07.μΊ‘μŠν™”
      • 08.κΈ°λŠ₯ 이동
      • 09.데이터 쑰직화
      • 10.쑰건뢀 둜직 κ°„μ†Œν™”
      • 11.API λ¦¬νŒ©ν„°λ§
      • 12.상속 닀루기
    • 객체지ν–₯의 사싀과 μ˜€ν•΄
      • 01.ν˜‘λ ₯ν•˜λŠ” κ°μ²΄λ“€μ˜ 곡동체
      • 02.μ΄μƒν•œ λ‚˜λΌμ˜ 객체
      • 03.νƒ€μž…κ³Ό 좔상화
      • 04.μ—­ν• , μ±…μž„, ν˜‘λ ₯
      • 05.μ±…μž„κ³Ό λ©”μ‹œμ§€
      • 06.객체 지도
      • 07.ν•¨κ»˜ λͺ¨μœΌκΈ°
      • 뢀둝.좔상화 기법
    • Clean Code
    • μžλ°” ORM ν‘œμ€€ JPA ν”„λ‘œκ·Έλž˜λ°
Powered by GitBook
On this page
  • Exception
  • μ˜ˆμ™Έ μ²˜λ¦¬μ™€ 였λ₯˜ νŽ˜μ΄μ§€
  • μ„œλΈ”λ¦Ώ μ˜ˆμ™Έ 처리
  • μ„œλΈ”λ¦Ώ 였λ₯˜ νŽ˜μ΄μ§€
  • μŠ€ν”„λ§ λΆ€νŠΈ 였λ₯˜ νŽ˜μ΄μ§€ 🌞
  • API μ˜ˆμ™Έ 처리
  • Spring Boot κΈ°λ³Έ 였λ₯˜ 처리
  • ExceptionResolver
  • Spring ExceptionResolver
  1. Lecture
  2. Spring MVC Part 2

05.Exception

Exception

μ˜ˆμ™Έ μ²˜λ¦¬μ™€ 였λ₯˜ νŽ˜μ΄μ§€

μ„œλΈ”λ¦Ώ μ˜ˆμ™Έ 처리

순수 μ„œλΈ”λ¦Ώ μ»¨ν…Œμ΄λ„ˆμ˜ μ˜ˆμ™Έ 처리 지원 방식

.

Exception

μžλ°” 직접 μ‹€ν–‰ μ‹œ μ˜ˆμ™Έ λ°œμƒ

  • μžλ°” 메인 λ©”μ„œλ“œλ₯Ό 직접 μ‹€ν–‰ν•  경우 main μ“°λ ˆλ“œ μ‹€ν–‰

  • main μ“°λ ˆλ“œ μ‹€ν–‰ 도쀑에 μ˜ˆμ™Έλ₯Ό μž‘μ§€ λͺ»ν•˜κ³  μ˜ˆμ™Έκ°€ λ˜μ Έμ§€λ©΄, μ˜ˆμ™Έ 정보λ₯Ό 남기고 ν•΄λ‹Ή μ“°λ ˆλ“œ μ’…λ£Œ

μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ μ˜ˆμ™Έ λ°œμƒ

@GetMapping("/error-ex")
    public void errorEx() {
    throw new RuntimeException("μ˜ˆμ™Έ λ°œμƒ!");
}
  • μ‚¬μš©μž μš”μ²­λ³„λ‘œ λ³„λ„μ˜ μ“°λ ˆλ“œκ°€ ν• λ‹Ήλ˜κ³ , μ„œλΈ”λ¦Ώ μ»¨ν…Œμ΄λ„ˆ μ•ˆμ—μ„œ μ‹€ν–‰

  • μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ μ˜ˆμ™Έ λ°œμƒ μ‹œ, try~catch 둜 μ˜ˆμ™Έλ₯Ό μž‘μ•„μ„œ μ²˜λ¦¬ν•˜λ©΄ λ¬Έμ œκ°€ μ—†μŒ.

  • ν•˜μ§€λ§Œ, μ˜ˆμ™Έλ₯Ό μž‘μ§€ λͺ»ν•˜κ³ , μ„œλΈ”λ¦Ώ λ°–μœΌλ‘œ μ˜ˆμ™Έκ°€ 전달될 경우 WAS(tomcat)κΉŒμ§€ μ˜ˆμ™Έ 전달

    WAS <- ν•„ν„° <- μ„œλΈ”λ¦Ώ <- 인터셉터 <- 컨트둀러(μ˜ˆμ™Έλ°œμƒ)
  • WAS λŠ” Exception μ˜ˆμ™Έκ°€ 올라였면 μ„œλ²„ λ‚΄λΆ€μ—μ„œ μ²˜λ¦¬ν•  수 μ—†λŠ” 였λ₯˜κ°€ λ°œμƒν•œ κ²ƒμœΌλ‘œ μΈμ§€ν•˜κ³  HTTP Status 500 – Internal Server Error λ°˜ν™˜

    # μŠ€ν”„λ§ λΆ€νŠΈκ°€ μ œκ³΅ν•˜λŠ” κΈ°λ³Έ μ˜ˆμ™Έ νŽ˜μ΄μ§€ OFF
    server.error.whitelabel.enabled=false

.

response.sendError(HTTP μƒνƒœ μ½”λ“œ, 였λ₯˜ λ©”μ‹œμ§€)

@GetMapping("/error-404")
public void error404(HttpServletResponse response) throws IOException {
    response.sendError(404, "404 였λ₯˜!"); // HTTP μƒνƒœ μ½”λ“œμ™€ 였λ₯˜ λ©”μ‹œμ§€ μΆ”κ°€ κ°€λŠ₯
}
  • 였λ₯˜ λ°œμƒ μ‹œ HttpServletResponse.sendError λ©”μ„œλ“œ μ‚¬μš© κ°€λŠ₯

    • sendError 호좜 μ‹œ λ°”λ‘œ μ˜ˆμ™Έκ°€ λ°œμƒν•˜λŠ” 것은 μ•„λ‹ˆμ§€λ§Œ, response 내뢀에 였λ₯˜ λ°œμƒ μƒνƒœλ₯Ό μ €μž₯ν•˜μ—¬ WAS(Servlet container) μ—κ²Œ 전달

    • WAS(Servlet container) λŠ” ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ 응닡 전에 response 에 sendError() 호좜 기둝 확인 ν›„, ν˜ΈμΆœλ˜μ—ˆλ‹€λ©΄ μ„€μ •ν•œ 였λ₯˜ μ½”λ“œμ— λ§žλŠ” κΈ°λ³Έ 였λ₯˜ νŽ˜μ΄μ§€ 좜λ ₯

  • sendError 흐름

    WAS(sendError 호좜 기둝 확인) <- ν•„ν„° <- μ„œλΈ”λ¦Ώ <- 인터셉터 <- 컨트둀러

μ„œλΈ”λ¦Ώ 였λ₯˜ νŽ˜μ΄μ§€

μ„œλΈ”λ¦Ώμ€ Exception 이 μ„œλΈ”λ¦Ώ λ°–μœΌλ‘œ μ „λ‹¬λ˜κ±°λ‚˜ response.sendError() 호좜 μ‹œ 각 상황에 맞좘 였λ₯˜ 처리 κΈ°λŠ₯ 제곡

μ„œλΈ”λ¦Ώ 였λ₯˜ νŽ˜μ΄μ§€ 등둝

@Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {

    @Override
    public void customize(ConfigurableWebServerFactory factory) {

        ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404"); // response.sendError(404)
        ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error-page/500"); // response.sendError(500)

        ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/error-page/500"); // RuntimeException λ˜λŠ” κ·Έ μžμ‹ νƒ€μž…μ˜ μ˜ˆμ™Έ

        factory.addErrorPages(errorPage404, errorPage500, errorPageEx);
    }
}
  • 였λ₯˜ νŽ˜μ΄μ§€λŠ” μ˜ˆμ™Έλ₯Ό λ‹€λ£° λ•Œ ν•΄λ‹Ή μ˜ˆμ™Έμ™€ κ·Έ μžμ‹ νƒ€μž…μ˜ 였λ₯˜λ₯Ό ν•¨κ»˜ 처리

였λ₯˜ 처리 컨트둀러

@Slf4j
@Controller
public class ErrorPageController {

    @RequestMapping("/error-page/404")
    public String errorPage404(HttpServletRequest request, HttpServletResponse response) {
        log.info("errorPage 404");
        return "error-page/404";
    }

    @RequestMapping("/error-page/500")
    public String errorPage500(HttpServletRequest request, HttpServletResponse response) {
        log.info("errorPage 500");
        return "error-page/500";
    }
}

였λ₯˜ νŽ˜μ΄μ§€ μš”μ²­ 흐름

# μ˜ˆμ™Έ λ°œμƒ 흐름
컨트둀러(μ˜ˆμ™Έ λ°œμƒ) βž” μŠ€ν”„λ§ 인터셉터 βž” μ„œλΈ”λ¦Ώ βž” ν•„ν„° βž” WAS

# sendError 흐름
컨트둀러(response.sendError()) βž” μŠ€ν”„λ§ 인터셉터 βž” μ„œλΈ”λ¦Ώ βž” ν•„ν„° βž” WAS(sendError 호좜 기둝 확인)

---

# 였λ₯˜ νŽ˜μ΄μ§€ μš”μ²­ 흐름
WAS '/error-page/500' μš”μ²­ βž” ν•„ν„° βž” μ„œλΈ”λ¦Ώ βž” μŠ€ν”„λ§ 인터셉터 βž” 컨트둀러(/error-page/500) βž” View
  • WAS κΉŒμ§€ μ˜ˆμ™Έκ°€ μ „νŒŒλ  경우, WAS λŠ” ν•΄λ‹Ή μ˜ˆμ™Έλ₯Ό μ²˜λ¦¬ν•˜λŠ” 였λ₯˜ νŽ˜μ΄μ§€ 정보 확인 ν›„ 였λ₯˜ νŽ˜μ΄μ§€λ₯Ό λ‹€μ‹œ μš”μ²­

  • WAS λŠ” 였λ₯˜ νŽ˜μ΄μ§€ μš”μ²­ μ‹œ 였λ₯˜ 정보λ₯Ό request.attribute 에 μΆ”κ°€ν•΄μ„œ 전달

private void printErrorInfo(HttpServletRequest request) {
    log.info("ERROR_EXCEPTION: ex=", request.getAttribute(RequestDispatcher.ERROR_EXCEPTION));
    log.info("ERROR_EXCEPTION_TYPE: {}", request.getAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE));
    log.info("ERROR_MESSAGE: {}", request.getAttribute(RequestDispatcher.ERROR_MESSAGE)); // ex의 경우 NestedServletException μŠ€ν”„λ§μ΄ ν•œλ²ˆ κ°μ‹Έμ„œ λ°˜ν™˜
    log.info("ERROR_REQUEST_URI: {}", request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI));
    log.info("ERROR_SERVLET_NAME: {}", request.getAttribute(RequestDispatcher.ERROR_SERVLET_NAME));
    log.info("ERROR_STATUS_CODE: {}", request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE));
    log.info("dispatchType={}", request.getDispatcherType());
}

DispatcherType 🌞

ν΄λΌμ΄μ–ΈνŠΈλ‘œ λΆ€ν„° λ°œμƒν•œ 정상 μš”μ²­μΈμ§€, 였λ₯˜ νŽ˜μ΄μ§€λ₯Ό 좜λ ₯ν•˜κΈ° μœ„ν•œ λ‚΄λΆ€ μš”μ²­μΈμ§€ κ΅¬λΆ„ν•˜κΈ° μœ„ν•΄ μ„œλΈ”λ¦Ώμ€ DispatcherType 정보 제곡

  • 였λ₯˜ λ°œμƒ μ‹œ 였λ₯˜ νŽ˜μ΄μ§€ 좜λ ₯을 μœ„ν•΄ WAS λ‚΄λΆ€μ—μ„œ ν•„ν„°, μ„œλΈ”λ¦Ώ, 인터셉터λ₯Ό λ‹€μ‹œ 호좜

  • 이미 초기 μš”μ²­μ—μ„œ 검증이 μ™„λ£Œλœ 뢀뢄은 재호좜 될 경우 λΉ„νš¨μœ¨μ 

.

DispatcherType

  • μ„œλΈ”λ¦Ώ μŠ€νŽ™μ€ μ‹€μ œ 고객이 μš”μ²­ν•œ 것인지, μ„œλ²„κ°€ λ‚΄λΆ€μ—μ„œ 였λ₯˜ νŽ˜μ΄μ§€λ₯Ό μš”μ²­ν•˜λŠ” 것인지 DispatcherType 으둜 ꡬ뢄할 수 μžˆλŠ” 방법을 제곡

public enum DispatcherType {
    FORWARD, // λ‹€λ₯Έ μ„œλΈ”λ¦Ώμ΄λ‚˜ JSP 호좜 βž” RequestDispatcher.forward(request, response)
    INCLUDE, // λ‹€λ₯Έ μ„œλΈ”λ¦Ώμ΄λ‚˜ JSP κ²°κ³Ό 포함 βž” RequestDispatcher.include(request, response)
    REQUEST, // ν΄λΌμ΄μ–ΈνŠΈ μš”μ²­
    ASYNC, // μ„œλΈ”λ¦Ώ 비동기 호좜
    ERROR // 였λ₯˜ μš”μ²­
}

.

필터와 DispatcherType

  • DispatcherType 둜그 ν•„ν„°

    @Slf4j
    public class LogFilter implements Filter {
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            log.info("log filter init");
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            String requestURI = httpRequest.getRequestURI();
            String uuid = UUID.randomUUID().toString();
            try {
                log.info("REQUEST [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);
                chain.doFilter(request, response);
            } catch (Exception e) {
                throw e;
            } finally {
                log.info("RESPONSE [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);
            }
        }
    
        @Override
        public void destroy() {
            log.info("log filter destroy");
        }
    }
  • 둜그 ν•„ν„° 등둝

    @Configuration
    public class DispatcherTypeWebConfig implements WebMvcConfigurer {
    
        @Bean
        public FilterRegistrationBean logFilter() {
            FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
            filterRegistrationBean.setFilter(new LogFilter());
            filterRegistrationBean.setOrder(1);
            filterRegistrationBean.addUrlPatterns("/*");
            // default: REQUEST. ν΄λΌμ΄μ–ΈνŠΈ μš”μ²­ μ‹œμ—λ§Œ ν•„ν„° 적용
            filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
            return filterRegistrationBean;
        }
    }

.

필터와 Interceptor

  • DispatcherType 둜그 인터셉터

    @Slf4j
    public class LogInterceptor implements HandlerInterceptor {
        public static final String LOG_ID = "logId";
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String requestURI = request.getRequestURI();
            String uuid = UUID.randomUUID().toString();
            request.setAttribute(LOG_ID, uuid);
            log.info("REQUEST [{}][{}][{}][{}]", uuid, request.getDispatcherType(), requestURI, handler);
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            log.info("postHandle [{}]", modelAndView);
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            String requestURI = request.getRequestURI();
            String logId = (String) request.getAttribute(LOG_ID);
            log.info("RESPONSE [{}][{}][{}]", logId, request.getDispatcherType(), requestURI);
            if (ex != null) {
                log.error("afterCompletion error!!", ex);
            }
        }
    }
  • 둜그 인터셉터 등둝

@Configuration
public class DispatcherTypeWebConfig implements WebMvcConfigurer {

    /**
     * ν•„ν„°λŠ” ν•„ν„° 등둝 μ‹œ νŠΉμ • DispatcherType 인 경우 ν•„ν„°κ°€ μ μš©λ˜λ„λ‘ 섀정이 κ°€λŠ₯ν–ˆμ§€λ§Œ,
     * μΈν„°μ…‰ν„°λŠ” μŠ€ν”„λ§μ΄ μ œκ³΅ν•˜λŠ” κΈ°λŠ₯μ΄λΌμ„œ DispatcherType 와 λ¬΄κ΄€ν•˜κ²Œ 항상 호좜
     * 
     * λŒ€μ‹  μΈν„°μ…‰ν„°μ˜ excludePathPatterns λ₯Ό μ‚¬μš©ν•΄μ„œ νŠΉμ • 경둜 μ œμ™Έ κ°€λŠ₯
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns(
                        "/css/**", "/*.ico"
                        , "/error", "/error-page/**" //였λ₯˜ νŽ˜μ΄μ§€ 경둜
                );
    }
}

.

DispatcherType 흐름

1. WAS(/error-ex, dispatchType=REQUEST) βž” ν•„ν„° βž” μ„œλΈ”λ¦Ώ βž” 인터셉터 βž” 컨트둀러
2. 컨트둀러(μ˜ˆμ™Έλ°œμƒ) βž” 인터셉터 βž” μ„œλΈ”λ¦Ώ βž” ν•„ν„° βž” WAS
3. WAS 였λ₯˜ νŽ˜μ΄μ§€ 확인
4. WAS(/error-page/500, dispatchType=ERROR) βž” ν•„ν„°(x) βž” μ„œλΈ”λ¦Ώ βž” 인터셉터(x) βž”
컨트둀러(/error-page/500) βž” View

μŠ€ν”„λ§ λΆ€νŠΈ 였λ₯˜ νŽ˜μ΄μ§€ 🌞

μŠ€ν”„λ§ λΆ€νŠΈλŠ” μ„œλΈ”λ¦Ώ 였λ₯˜ νŽ˜μ΄μ§€ ν˜ΈμΆœμ— ν•„μš”ν–ˆλ˜ μ•„λž˜ λ³΅μž‘ν•œ 과정을 기본으둜 제곡

  • WebServerCustomizer λ§Œλ“€κΈ°

  • μ˜ˆμ™Έ μ’…λ₯˜μ— 따라 ErrorPage μΆ”κ°€ βž” ErrorPage μžλ™ 등둝

    • /error 경둜λ₯Ό κΈ°λ³Έ 였λ₯˜ νŽ˜μ΄μ§€λ‘œ μ„€μ •

    • μ„œλΈ”λ¦Ώ λ°–μœΌλ‘œ μ˜ˆμ™Έκ°€ λ˜μ Έμ§€κ±°λ‚˜, response.sendError(...) 호좜 μ‹œ λͺ¨λ“  였λ₯˜λŠ” /error 호좜

    • ErrorMvcAutoConfiguration ν΄λž˜μŠ€κ°€ 였λ₯˜ νŽ˜μ΄μ§€λ₯Ό μžλ™μœΌλ‘œ λ“±λ‘ν•˜λŠ” μ—­ν• 

  • μ˜ˆμ™Έ 처리용 컨트둀러(ErrorPageController) 생성 βž” μžλ™ 등둝(BasicErrorController)

    • ErrorPage μ—μ„œ λ“±λ‘ν•œ /error λ₯Ό λ§€ν•‘ν•΄μ„œ μ²˜λ¦¬ν•˜λŠ” 컨트둀러

.

BasicErrorController

  • 기본적인 였λ₯˜ νŽ˜μ΄μ§€ 둜직이 λͺ¨λ‘ κ΅¬ν˜„

  • κ°œλ°œμžλŠ” 였λ₯˜ νŽ˜μ΄μ§€ ν™”λ©΄λ§Œ BasicErrorController κ°€ μ œκ³΅ν•˜λŠ” λ£°κ³Ό μš°μ„ μˆœμœ„μ— λ”°λΌμ„œ 등둝

    • 정적 HTML 일 경우 정적 λ¦¬μ†ŒμŠ€, λ·° ν…œν”Œλ¦Ώμ„ μ‚¬μš©ν•œ 동적 였λ₯˜ 화면일 경우 λ·° ν…œν”Œλ¦Ώ κ²½λ‘œμ— 였λ₯˜ νŽ˜μ΄μ§€ 파일 생성

.

BasicErrorController View 선택 μš°μ„ μˆœμœ„

.1. λ·° ν…œν”Œλ¦Ώ

  • resources/templates/error/500.html

  • resources/templates/error/5xx.html

.2. 정적 λ¦¬μ†ŒμŠ€(static, public)

  • resources/static/error/400.html

  • resources/static/error/404.html

  • resources/static/error/4xx.html

.3. 적용 λŒ€μƒμ΄ 없을 λ•Œ λ·° 이름(error)

  • resources/templates/error.html

ν•΄λ‹Ή 경둜 μœ„μΉ˜μ— HTTP μƒνƒœ μ½”λ“œ μ΄λ¦„μ˜ λ·° νŒŒμΌμ„ λ„£μ–΄λ‘μž.

λ·° ν…œν”Œλ¦Ώμ΄ 정적 λ¦¬μ†ŒμŠ€λ³΄λ‹€ μš°μ„ μˆœμœ„κ°€ λ†’κ³ ,

404, 500 처럼 ꡬ체적인 것이 5xx처럼 덜 ꡬ체적인 것 보닀 μš°μ„ μˆœμœ„κ°€ λ†’λ‹€.

.

BasicErrorController 제곡 κΈ°λ³Έ 정보

  • κΈ°λ³Έ 정보λ₯Ό model 에 λ‹΄μ•„ View 에 전달

timestamp: Fri Feb 05 00:00:00 KST 2021
path: `/hello` (Client μš”μ²­ 경둜 )
status: 400
message: Validation failed for object='data'. Error count: 1
error: Bad Request
exception: org.springframework.validation.BindException
errors: Errors(BindingResult)
trace: μ˜ˆμ™Έ trace
  • message, exception, errors, trace μ •λ³΄λŠ” λ³΄μ•ˆμƒ default 둜 포함이 λ˜μ–΄μžˆμ§€ μ•ŠμŒ

    • properties 섀정을 톡해 였λ₯˜ 정보λ₯Ό model 에 포함할지 μ—¬λΆ€ 선택

    # exception 포함 μ—¬λΆ€(true, false)
    server.error.include-exception=true
    # message 포함 μ—¬λΆ€
    server.error.include-message=always
    # trace 포함 μ—¬λΆ€
    server.error.include-stacktrace=always
    # errors 포함 μ—¬λΆ€
    server.error.include-binding-errors=always
    • never: μ‚¬μš©ν•˜μ§€ μ•ŠμŒ

    • always: 항상 μ‚¬μš©

    • on_param: νŒŒλΌλ―Έν„°κ°€ μžˆμ„ λ•Œ ν•΄λ‹Ή 정보 λ…ΈμΆœ

      • HTTP μš”μ²­μ‹œ νŒŒλΌλ―Έν„°(?message=&errorsa=&trace=)λ₯Ό μ „λ‹¬ν•˜λ©΄ ν•΄λ‹Ή 정보듀이 model 에 담겨 λ·° ν…œν”Œλ¦Ώμ— 좜λ ₯

      • 운영 μ„œλ²„μ—μ„œλŠ” λΉ„κΆŒμž₯

μ‹€λ¬΄μ—μ„œλŠ” 이 정보듀을 λ…ΈμΆœν•˜λ©΄ μ•ˆλœλ‹€.

μ‚¬μš©μžμ—κ²ŒλŠ” κΉ”λ”ν•œ 였λ₯˜ νŽ˜μ΄μ§€μ™€ 고객이 이해할 수 μžˆλŠ” κ°„λ‹¨ν•œ 였λ₯˜ λ©”μ‹œμ§€λ₯Ό 보여주고,

였λ₯˜λŠ” μ„œλ²„μ— 둜그둜 λ‚¨κ²¨μ„œ 둜그둜 ν™•μΈν•˜μž.

.

μŠ€ν”„λ§ λΆ€νŠΈ 였λ₯˜ κ΄€λ ¨ μ˜΅μ…˜

# 였λ₯˜ 처리 화면을 λͺ» 찾을 경우, μŠ€ν”„λ§ whitelabel 였λ₯˜ νŽ˜μ΄μ§€ 적용
server.error.whitelabel.enabled=true

# 였λ₯˜ νŽ˜μ΄μ§€ 경둜
# μŠ€ν”„λ§μ΄ μžλ™ λ“±λ‘ν•˜λŠ” μ„œλΈ”λ¦Ώ κΈ€λ‘œλ²Œ 였λ₯˜ νŽ˜μ΄μ§€ κ²½λ‘œμ™€ BasicErrorController 였λ₯˜ 컨트둀러 κ²½λ‘œμ— ν•¨κ»˜ μ‚¬μš©
server.error.path=/error

ν™•μž₯ 포인트

  • μ—λŸ¬ 곡톡 처리 컨트둀러의 κΈ°λŠ₯을 λ³€κ²½ν•˜κ³  싢을 경우 ErrorController μΈν„°νŽ˜μ΄μŠ€λ₯Ό 상속 λ°›μ•„μ„œ κ΅¬ν˜„ν•˜κ±°λ‚˜, BasicErrorController λ₯Ό 상속 λ°›μ•„μ„œ κΈ°λŠ₯을 μΆ”κ°€ν•΄λ³΄μž.

μŠ€ν”„λ§ λΆ€νŠΈ κΈ°λ³Έ 제곡 였λ₯˜ νŽ˜μ΄μ§€λ₯Ό ν™œμš©ν•˜λ©΄ 였λ₯˜ νŽ˜μ΄μ§€ κ΄€λ ¨ λŒ€λΆ€λΆ„μ˜ λ¬Έμ œλŠ” μ†μ‰½κ²Œ ν•΄κ²° κ°€λŠ₯ν•˜λ‹€.

API μ˜ˆμ™Έ 처리

였λ₯˜ νŽ˜μ΄μ§€λŠ” λ‹¨μˆœνžˆ κ³ κ°μ—κ²Œ 였λ₯˜ 화면을 보여주면 λ˜μ§€λ§Œ, API λŠ” 각 였λ₯˜ 상황에 λ§žλŠ” 였λ₯˜ 응닡 μŠ€νŽ™μ„ μ •ν•˜κ³ , JSON으둜 데이터λ₯Ό 응닡해 μ£Όμ–΄μ•Ό ν•œλ‹€.

produces μ„€μ •

@RequestMapping(value = "/error-page/500", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> errorPage500Api(HttpServletRequest request, HttpServletResponse response) {
    // ...
    return new ResponseEntity(result, HttpStatus.valueOf(statusCode));
}
  • produces = MediaType.APPLICATION_JSON_VALUE

    • ν΄λΌμ΄μ–ΈνŠΈκ°€ μš”μ²­ν•˜λŠ” HTTP Header Accept 값이 application/json 일 λ•Œ ν•΄λ‹Ή λ©”μ„œλ“œ 호좜

  • ResponseEntity λŠ” λ©”μ‹œμ§€ 컨버터가 λ™μž‘ν•˜λ©΄μ„œ ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ JSON ꡬ쑰둜 λ³€ν™˜ν•˜μ—¬ λ°˜ν™˜

Spring Boot κΈ°λ³Έ 였λ₯˜ 처리

API μ˜ˆμ™Έ μ²˜λ¦¬λ„ μŠ€ν”„λ§ λΆ€νŠΈκ°€ μ œκ³΅ν•˜λŠ” κΈ°λ³Έ 였λ₯˜ 방식을 μ‚¬μš©

BasicErrorController μ½”λ“œ 일뢀

// ν΄λΌμ΄μ–ΈνŠΈ μš”μ²­μ˜ Accept 해더 값이 text/html 인 경우 호좜(view 제곡)
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { ... }

// κ·Έμ™Έ κ²½μš°μ— 호좜(ResponseEntity 둜 HTTP Body 에 JSON 데이터 λ°˜ν™˜)
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { ... }
  • μŠ€ν”„λ§ λΆ€νŠΈμ˜ κΈ°λ³Έ 섀정은 였λ₯˜ λ°œμƒμ‹œ /error λ₯Ό 였λ₯˜ νŽ˜μ΄μ§€λ‘œ μš”μ²­

  • BasicErrorController λŠ” /error 경둜λ₯Ό 기본으둜 λ°›μŒ.(server.error.path 둜 μˆ˜μ • κ°€λŠ₯

BasicErrorController λŠ” HTML 였λ₯˜ νŽ˜μ΄μ§€λ₯Ό μ œκ³΅ν•˜λŠ” 경우 맀우 편리

단, API λŠ” 각 μ»¨νŠΈλ‘€λŸ¬λ‚˜ μ˜ˆμ™Έλ§ˆλ‹€ μ„œλ‘œ λ‹€λ₯Έ 응닡 κ²°κ³Όλ₯Ό 좜λ ₯ν•΄μ•Ό ν•˜λ―€λ‘œ @ExceptionHandler μ‚¬μš© ꢌμž₯

ExceptionResolver

HandlerExceptionResolver

public interface HandlerExceptionResolver {
    ModelAndView resolveException(
        HttpServletRequest request, 
        HttpServletResponse response, 
        Object handler, // ν•Έλ“€λŸ¬(컨트둀러) 정보
        Exception ex // ν•Έλ“€λŸ¬(컨트둀러)μ—μ„œ λ°œμƒν•œ μ˜ˆμ™Έ
    ); 
}
  • μŠ€ν”„λ§ MVC λŠ” 컨트둀러(ν•Έλ“€λŸ¬) λ°–μœΌλ‘œ μ˜ˆμ™Έκ°€ λ˜μ Έμ§„ 경우 μ˜ˆμ™Έλ₯Ό ν•΄κ²°ν•˜κ³ , λ™μž‘μ„ μƒˆλ‘œ μ •μ˜ν•  수 μžˆλŠ” HandlerExceptionResolver 제곡

    • ExceptionResolver 적용 μ „ μ˜ˆμ™Έμ²˜λ¦¬

    • ExceptionResolver 적용 ν›„ μ˜ˆμ™Έμ²˜λ¦¬

  • μ°Έκ³ . ExceptionResolver 둜 μ˜ˆμ™Έλ₯Ό 해결해도 postHandle() 호좜 X

HandlerExceptionResolver Interface κ΅¬ν˜„

@Slf4j
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
    /**
     * ModelAndView λ₯Ό λ°˜ν™˜ν•˜λŠ” μ΄μœ λŠ” try-catch 처럼 Exception 을 μ²˜λ¦¬ν•΄μ„œ 정상 νλ¦„μ²˜λŸΌ λ³€κ²½ν•˜μ—¬ μ˜ˆμ™Έλ₯Ό ν•΄κ²°ν•˜λŠ” 것이 λͺ©μ 
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        try {
            // IllegalArgumentException λ°œμƒ μ‹œ μ˜ˆμ™Έλ₯Ό HTTP μƒνƒœ μ½”λ“œ 400으둜 전달
            if (ex instanceof IllegalArgumentException) {
                log.info("IllegalArgumentException resolver to 400");
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
                
                //1. 빈 ModelAndView λ°˜ν™˜ μ‹œ λ·° λ Œλ”λ§μ„ ν•˜μ§€ μ•Šκ³  정상 νλ¦„μœΌλ‘œ μ„œλΈ”λ¦Ώ λ°˜ν™˜
                //2. ModelAndView 에 View, Model λ“±μ˜ 정보λ₯Ό μ§€μ •ν•˜μ—¬ λ°˜ν™˜ν•˜λ©΄ λ·° λ Œλ”λ§
                return new ModelAndView();
            }
        } catch (IOException e) {
            log.error("resolver ex", e);
        }

        //3. null λ°˜ν™˜ μ‹œ λ‹€μŒ ExceptionResolver μ°Ύμ•„μ„œ μ‹€ν–‰
        //처리 κ°€λŠ₯ν•œ ExceptionResolver κ°€ 없을 경우 κΈ°μ‘΄ λ°œμƒν•œ μ˜ˆμ™Έλ₯Ό μ„œλΈ”λ¦Ώ λ°–μœΌλ‘œ 전달
        return null;
    }
}

.

HandlerExceptionResolver λ°˜ν™˜ 값에 λ”°λ₯Έ DispatcherServlet λ™μž‘ 방식

  • 빈 ModelAndView

    • new ModelAndView() 처럼 빈 ModelAndView λ₯Ό λ°˜ν™˜ν•˜λ©΄ λ·°λ₯Ό λ Œλ”λ§ ν•˜μ§€μ•Šκ³ , 정상 νλ¦„μœΌλ‘œ μ„œλΈ”λ¦Ώμ΄ 리턴

  • ModelAndView μ§€μ •

    • ModelAndView 에 View, Model λ“±μ˜ 정보λ₯Ό μ§€μ •ν•΄μ„œ λ°˜ν™˜ν•˜λ©΄ λ·°λ₯Ό λ Œλ”λ§

  • nul

    • null 을 λ°˜ν™˜ν•˜λ©΄, λ‹€μŒ ExceptionResolver λ₯Ό μ°Ύμ•„μ„œ μ‹€ν–‰

    • λ§Œμ•½ μ²˜λ¦¬ν•  수 μžˆλŠ” ExceptionResolver κ°€ μ—†μœΌλ©΄ μ˜ˆμ™Έ μ²˜λ¦¬κ°€ μ•ˆλ˜κ³ , 기쑴에 λ°œμƒν•œ μ˜ˆμ™Έλ₯Ό μ„œλΈ”λ¦Ώ λ°–μœΌλ‘œ 던짐

.

ExceptionResolver ν™œμš©

  • μ˜ˆμ™Έ μƒνƒœ μ½”λ“œ λ³€ν™˜

    • μ˜ˆμ™Έλ₯Ό response.sendError(..) 호좜둜 λ³€κ²½ν•΄μ„œ μ„œλΈ”λ¦Ώμ—μ„œ μƒνƒœ μ½”λ“œμ— λ”°λ₯Έ 였λ₯˜λ₯Ό μ²˜λ¦¬ν•˜λ„λ‘ μœ„μž„

    • 이후 WAS λŠ” μ„œλΈ”λ¦Ώ 였λ₯˜ νŽ˜μ΄μ§€λ₯Ό μ°Ύμ•„μ„œ λ‚΄λΆ€ 호좜(default. /error)

    • ex. μ‹€μ œ μ„œλ²„μ—μ„œλŠ” 500 μ—λŸ¬κ°€ λ°œμƒν•˜μ˜€μ§€λ§Œ Client μ—κ²ŒλŠ” 4xx μ½”λ“œ 전달

  • λ·° ν…œν”Œλ¦Ώ 처리

    • ModelAndView 에 값을 μ±„μ›Œμ„œ μ˜ˆμ™Έμ— λ”°λ₯Έ μƒˆλ‘œμš΄ 였λ₯˜ 화면을 λ·° λ Œλ”λ§μ„ 톡해 κ³ κ°μ—κ²Œ 제곡

    • return new ModelAndView("error/400");

  • API 응닡 처리

    • response.getWriter().println("hello"); 처럼 HTTP 응닡 바디에 직접 데이터λ₯Ό λ„£μ–΄μ„œ 전달

    • 여기에 JSON 으둜 μ‘λ‹΅ν•˜λ©΄ API 응닡 처리

.

WebMvcConfigurer 에 등둝

@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
    resolvers.add(new MyHandlerExceptionResolver());
}
  • configureHandlerExceptionResolvers μ‚¬μš© μ‹œ μŠ€ν”„λ§μ΄ 기본으둜 λ“±λ‘ν•˜λŠ” ExceptionResolver κ°€ μ œκ±°λ˜λ―€λ‘œ extendHandlerExceptionResolvers λ₯Ό μ‚¬μš©

ExceptionResolver 적용

Exception

public class UserException extends RuntimeException {
    public UserException() {
        super();
    }

    public UserException(String message) {
        super(message);
    }

    public UserException(String message, Throwable cause) {
        super(message, cause);
    }

    public UserException(Throwable cause) {
        super(cause);
    }

    protected UserException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

...

// μ˜ˆμ™Έ 생성
throw new UserException("μ‚¬μš©μž 였λ₯˜");

HandlerExceptionResolver κ΅¬ν˜„

@Slf4j
public class UserHandlerExceptionResolver implements HandlerExceptionResolver {
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        try {
            if (ex instanceof UserException) {
                log.info("UserException resolver to 400");
                String acceptHeader = request.getHeader("accept");
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);

                // HTTP μš”μ²­ ν•΄λ”μ˜ ACCEPT 값이 application/json 일 경우 JSON 응닡
                if ("application/json".equals(acceptHeader)) {
                    Map<String, Object> errorResult = new HashMap<>();
                    errorResult.put("ex", ex.getClass());
                    errorResult.put("message", ex.getMessage());
                    String result = objectMapper.writeValueAsString(errorResult);
                    response.setContentType("application/json");
                    response.setCharacterEncoding("utf-8");
                    response.getWriter().write(result);

                    return new ModelAndView();
                } else { // κ·Έ μ™Έμ˜ κ²½μš°μ—λŠ” TEXT/HTML 였λ₯˜ νŽ˜μ΄μ§€ λ…ΈμΆœ
                    return new ModelAndView("error/500");
                }
            }
        } catch (IOException e) {
            log.error("resolver ex", e);
        }

        return null;
    }
}

WebConfig 에 HandlerExceptionResolver 등둝

@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
    resolvers.add(new MyHandlerExceptionResolver());
    resolvers.add(new UserHandlerExceptionResolver());
}

정리.

μ»¨νŠΈλ‘€λŸ¬μ—μ„œ μ˜ˆμ™Έκ°€ λ°œμƒν•΄λ„ ExceptionResolver μ—μ„œ μ˜ˆμ™Έλ₯Ό 처리

μ˜ˆμ™Έκ°€ λ°œμƒν•΄λ„ μ„œλΈ”λ¦Ώ μ»¨ν…Œμ΄λ„ˆκΉŒμ§€ μ˜ˆμ™Έκ°€ μ „λ‹¬λ˜μ§€ μ•Šκ³ (결과적으둜 WAS μž…μž₯μ—μ„œλŠ” 정상 처리)

μŠ€ν”„λ§ MVC μ—μ„œ μ˜ˆμ™Έ μ²˜λ¦¬λŠ” μ’…λ£Œ

Spring ExceptionResolver

Spring Boot 기본적으둜 μ œκ³΅ν•˜λŠ” ExceptionResolver

HandlerExceptionResolverComposite 에 μ•„λž˜ μˆœμ„œλ‘œ 등둝

  • ExceptionHandlerExceptionResolver

    • @ExceptionHandler 처리

    • API μ˜ˆμ™Έ μ²˜λ¦¬λŠ” λŒ€λΆ€λΆ„ 이 κΈ°λŠ₯으둜 ν•΄κ²°

  • ResponseStatusExceptionResolver

    • HTTP μƒνƒœ μ½”λ“œ λ³€κ²½

  • DefaultHandlerExceptionResolver

    • μŠ€ν”„λ§ λ‚΄λΆ€ κΈ°λ³Έ μ˜ˆμ™Έ 처리

ExceptionHandler 🌞

ExceptionHandlerExceptionResolver

μŠ€ν”„λ§μ€ API μ˜ˆμ™Έ 처리 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ @ExceptionHandler λ₯Ό μ‚¬μš©ν•œ νŽΈλ¦¬ν•œ μ˜ˆμ™Έ 처리 κΈ°λŠ₯ 제곡

  • 각 μ‹œμŠ€ν…œλ§ˆλ‹€ λ‹€λ₯Έ 응닡 λͺ¨μ–‘κ³Ό μŠ€νŽ™

  • μ˜ˆμ™Έμ— λ”°λ₯Έ 각기 λ‹€λ₯Έ 데이터 응닡

  • μ»¨νŠΈλ‘€λŸ¬μ— 따라 λ‹€λ₯Έ μ˜ˆμ™Έ 응닡

  • ModelAndView κ°€ μ•„λ‹Œ Json ν˜•νƒœλ‘œ λ°˜ν™˜

  • λ“±.. μ„Έλ°€ν•œ μ œμ–΄ ν•„μš”

.

@ExceptionHandler μ˜ˆμ™Έ 처리 방법

  • @ExceptionHandler μ„ μ–Έ ν›„ ν•΄λ‹Ή μ»¨νŠΈλ‘€λŸ¬μ—μ„œ μ²˜λ¦¬ν•˜κ³  싢은 μ˜ˆμ™Έ μ§€μ •

  • ν•΄λ‹Ή μ»¨νŠΈλ‘€λŸ¬μ—μ„œ μ˜ˆμ™Έ λ°œμƒ μ‹œ ν•΄λ‹Ή λ©”μ„œλ“œκ°€ 호좜

  • μ§€μ •ν•œ μ˜ˆμ™Έ λ˜λŠ” ν•˜μœ„ μžμ‹ 클래슀 λͺ¨λ‘ 처리

    /**
     * λΆ€λͺ¨, μžμ‹ 클래슀 λͺ¨λ‘ μ§€μ •λ˜μ–΄ μžˆμ„ 경우 μžμ„Έν•œ 것이 μš°μ„ κΆŒ
     */
    @ExceptionHandler(λΆ€λͺ¨μ˜ˆμ™Έ.class)
    public String λΆ€λͺ¨μ˜ˆμ™Έμ²˜λ¦¬()(λΆ€λͺ¨μ˜ˆμ™Έ e) {}
    
    @ExceptionHandler(μžμ‹μ˜ˆμ™Έ.class)
    public String μžμ‹μ˜ˆμ™Έμ²˜λ¦¬()(μžμ‹μ˜ˆμ™Έ e) {}
  • λ‹€μ–‘ν•œ μ˜ˆμ™Έλ₯Ό ν•œ λ²ˆμ— 처리 κ°€λŠ₯

    @ExceptionHandler({AException.class, BException.class})
    public String ex(Exception e) {
        log.info("exception e", e);
    }
  • μ˜ˆμ™Έ μƒλž΅

    /**
     * μ˜ˆμ™Έ μƒλž΅ μ‹œ λ©”μ„œλ“œ νŒŒλΌλ―Έν„°μ˜ μ˜ˆμ™Έ(UserException)κ°€ μ§€μ •
     */
    @ExceptionHandler
    public ResponseEntity<ErrorResult> userExHandle(UserException e) {}
  • νŒŒλΌλ―Έν„°μ™€ 응닡

    • λ‹€μ–‘ν•œ νŒŒλΌλ―Έν„°μ™€ 응닡 μ§€μ • κ°€λŠ₯

.

@ExceptionHandler μ‹€ν–‰ 흐름

throw new IllegalArgumentException("잘λͺ»λœ μž…λ ₯ κ°’");

...

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandle(IllegalArgumentException e) {
    return new ErrorResult("BAD", e.getMessage());
}
  • 컨트둀러 호좜 결과둜 μ˜ˆμ™Έ(IllegalArgumentException)κ°€ 컨트둀러 λ°–μœΌλ‘œ 던져짐

  • DispatcherServlet 을 거쳐 μ˜ˆμ™Έ λ°œμƒμœΌλ‘œ ExceptionResolver μž‘λ™

    • κ°€μž₯ μš°μ„ μˆœμœ„κ°€ 높은 ExceptionHandlerExceptionResolver μ‹€ν–‰

  • ExceptionHandlerExceptionResolver λŠ” ν•΄λ‹Ή μ»¨νŠΈλ‘€λŸ¬μ— IllegalArgumentException 을 μ²˜λ¦¬ν•  수 μžˆλŠ” @ExceptionHandler κ°€ μžˆλŠ”μ§€ 확인

  • @ExceptionHandler μ„ μ–Έ λ©”μ„œλ“œ μ‹€ν–‰

    • @RestController μ΄λ―€λ‘œ @ResponseBody 적용 βž” HTTP 컨버터가 μ‚¬μš©λ˜κ³  JSON 응닡

  • @ResponseStatus(HttpStatus.BAD_REQUEST) λ₯Ό μ§€μ •ν–ˆμœΌλ―€λ‘œ HTTP μƒνƒœ μ½”λ“œ 400 응닡

    • μ„œλΈ”λ¦Ώ μ»¨ν…Œμ΄λ„ˆκΉŒμ§€ λ‚΄λ €κ°€μ§€ μ•Šκ³  정상 νλ¦„μœΌλ‘œ λ°˜ν™˜

.

상황에 λ”°λ₯Έ @ExceptionHandler ν™œμš©

/**
 * 에외 처리용 클래슀λ₯Ό λ§Œλ“€μ–΄μ„œ μ‚¬μš©ν•˜λŠ” 경우
 * ν˜„μž¬ Controller μ—μ„œ IllegalArgumentException λ°œμƒ μ‹œ 호좜
 * 
 * @ResponseStatus λŠ” μ• λ…Έν…Œμ΄μ…˜μ΄λ―€λ‘œ HTTP 응닡 μ½”λ“œλ₯Ό λ™μ μœΌλ‘œ λ³€κ²½ λΆˆκ°€
 */
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandle(IllegalArgumentException e) {
    return new ErrorResult("BAD", e.getMessage());
}

/**
 * ν˜„μž¬ Controller μ—μ„œ UserException λ°œμƒ μ‹œ 호좜
 * 
 * ResponseEntity λ₯Ό μ‚¬μš©ν•΄μ„œ HTTP λ©”μ‹œμ§€ 바디에 직접 응닡(HTTP 컨버터 μ‚¬μš©)
 * HTTP 응닡 μ½”λ“œλ₯Ό ν”„λ‘œκ·Έλž˜λ°ν•΄μ„œ λ™μ μœΌλ‘œ λ³€κ²½ κ°€λŠ₯
 */
@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandle(UserException e) {
    ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
    return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
}

/**
 * ν˜„μž¬ Controller μ—μ„œ RuntimeException(Exception 의 μžμ‹ 클래슀) λ°œμƒ μ‹œ 호좜
 * 
 * μ²˜λ¦¬λ˜μ§€ λͺ»ν•œ 남은 μ˜ˆμ™Έλ₯Ό 처리
 */
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandle(Exception e) {
    return new ErrorResult("EX", "λ‚΄λΆ€ 였λ₯˜");
}

/**
 * ModelAndView λ₯Ό μ‚¬μš©ν•΄μ„œ 였λ₯˜ ν™”λ©΄(HTML) 응닡
 */
@ExceptionHandler(ViewException.class)
public ModelAndView ex(ViewException e) {
    return new ModelAndView("error");
}

@ControllerAdvice 🌞

μ—¬λŸ¬ μ»¨νŠΈλ‘€λŸ¬μ—μ„œ λ°œμƒν•˜λŠ” 였λ₯˜λ₯Ό λͺ¨μ•„μ„œ 처리

  • @ExceptionHandler λ₯Ό μ‚¬μš©ν•΄μ„œ μ˜ˆμ™Έλ₯Ό κΉ”λ”ν•˜κ²Œ μ²˜λ¦¬κ°€ κ°€λŠ₯ν•˜μ§€λ§Œ, 정상 μ½”λ“œμ™€ μ˜ˆμ™Έ 처리 μ½”λ“œκ°€ ν•˜λ‚˜μ˜ μ»¨νŠΈλ‘€λŸ¬μ— μ„žμ—¬ μžˆλŠ” 단점이 쑴재

  • @ControllerAdvice λ˜λŠ” @RestControllerAdvice λ₯Ό μ‚¬μš©ν•΄μ„œ 뢄리해 보자.

@ControllerAdvice

@RestControllerAdvice
public class ExControllerAdvice {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public ErrorResult illegalExHandle(IllegalArgumentException e) {
        return new ErrorResult("BAD", e.getMessage());
    }

    @ExceptionHandler
    public ResponseEntity<ErrorResult> userExHandle(UserException e) {
        ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
        return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler
    public ErrorResult exHandle(Exception e) {
        return new ErrorResult("EX", "λ‚΄λΆ€ 였λ₯˜");
    }
}
  • λŒ€μƒμœΌλ‘œ μ§€μ •ν•œ μ—¬λŸ¬ μ»¨νŠΈλ‘€λŸ¬μ— @ExceptionHandler, @InitBinder κΈ°λŠ₯을 λΆ€μ—¬

    • // Target all Controllers annotated with @RestController
      @ControllerAdvice(annotations = RestController.class)
      public class ExampleAdvice1 {}
      
      // Target all Controllers within specific packages
      @ControllerAdvice("org.example.controllers")
      public class ExampleAdvice2 {}
      
      // Target all Controllers assignable to specific classes
      @ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
      public class ExampleAdvice3 {}
  • @ControllerAdvice 에 λŒ€μƒμ„ μ§€μ •ν•˜μ§€ μ•ŠμœΌλ©΄ λͺ¨λ“  μ»¨νŠΈλ‘€λŸ¬μ— 적용(κΈ€λ‘œλ²Œ 적용)

  • @RestControllerAdvice λŠ” @ControllerAdvice 와 λ™μΌν•˜κ³ , @ResponseBody κ°€ μΆ”κ°€

    • @Controller, @RestController 차이와 동일

@ExceptionHandler 와 @ControllerAdvice λ₯Ό μ‘°ν•©ν•˜λ©΄ μ˜ˆμ™Έλ₯Ό κΉ”λ”ν•˜κ²Œ ν•΄κ²° κ°€λŠ₯

ResponseStatusExceptionResolver

μ˜ˆμ™Έμ— 따라 HTTP μƒνƒœ μ½”λ“œ μ§€μ •

  • @ResponseStatus κ°€ λ‹¬λ €μžˆλŠ” μ˜ˆμ™Έ

  • ResponseStatusException μ˜ˆμ™Έ

.

@ResponseStatus μ˜ˆμ™Έ

@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "잘λͺ»λœ μš”μ²­ 였λ₯˜")
public class BadRequestException extends RuntimeException { }

...

throw new BadRequestException();
  • ν•΄λ‹Ή μ˜ˆμ™Έκ°€ 컨트둀러 λ°–μœΌλ‘œ λ„˜μ–΄κ°€λ©΄ ResponseStatusExceptionResolver μ˜ˆμ™Έκ°€ ν•΄λ‹Ή μ• λ…Έν…Œμ΄μ…˜μ„ ν™•μΈν•΄μ„œ HTTP μƒνƒœ μ½”λ“œλ₯Ό λ³€κ²½(HttpStatus.BAD_REQUEST(400))ν•˜κ³ , λ©”μ‹œμ§€ 포함

    • ResponseStatusExceptionResolver μ—μ„œ response.sendError(statusCode, resolvedReason) 호좜

  • sendError(400) 호좜둜 WAS μ—μ„œ λ‹€μ‹œ 였λ₯˜ νŽ˜μ΄μ§€(/error) λ‚΄λΆ€ μš”μ²­

  • reason 을 MessageSource μ—μ„œ μ°ΎλŠ” λ©”μ„Έμ§€ κΈ°λŠ₯ 제곡 βž” reason = "error.bad"

`

ResponseStatusException μ˜ˆμ™Έ

throw new ResponseStatusException(HttpStatus.NOT_FOUND, "error.bad", new IllegalArgumentException());
  • 직접 λ³€κ²½ν•  수 μ—†λŠ” μ˜ˆμ™Έμ— ResponseStatusException 적용

    • @ResponseStatus λŠ” μ• λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜λ―€λ‘œ 직접 (쑰건에 따라 λ™μ μœΌλ‘œ)λ³€κ²½ν•  수 μ—†λŠ” μ˜ˆμ™Έμ—λŠ” 적용 λΆˆκ°€

DefaultHandlerExceptionResolver

  • μŠ€ν”„λ§ λ‚΄λΆ€μ—μ„œ λ°œμƒν•˜λŠ” μŠ€ν”„λ§ μ˜ˆμ™Έ 처리

    • μŠ€ν”„λ§ λ‚΄λΆ€ 였λ₯˜λ₯Ό μ–΄λ–»κ²Œ μ²˜λ¦¬ν• μ§€μ— λŒ€ν•œ λ§Žμ€ λ‚΄μš©μ΄ μ •μ˜

  • TypeMismatchException 으둜 λ°œμƒν•˜λŠ” 500 였λ₯˜λ₯Ό DefaultHandlerExceptionResolver κ°€ 400 였λ₯˜λ‘œ λ³€κ²½

DefaultHandlerExceptionResolver.handleTypeMismatch

protected ModelAndView handleTypeMismatch(TypeMismatchException ex, HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {
  // response.sendError() λ₯Ό 톡해 문제 ν•΄κ²°.
  // sendError(400) λ₯Ό ν˜ΈμΆœν–ˆμœΌλ―€λ‘œ WAS μ—μ„œ λ‹€μ‹œ 였λ₯˜ νŽ˜μ΄μ§€(/error) λ‚΄λΆ€ μš”μ²­
  response.sendError(HttpServletResponse.SC_BAD_REQUEST);
  return new ModelAndView();
}

Last updated 1 year ago

Result
Result

βž” 보톡 νŒ¨ν‚€μ§€λͺ… 정도 μ§€μ •

@ExceptionHandler's Method Arguments And Return Values
λŒ€μƒ 컨트둀러 μ§€μ •