| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- 페이징
- JPA
- 쿠버네티스
- docker
- securitycontextholderfilter
- docker compose
- JdbcTemplate
- JPQL
- DLQ
- 스프링 부트
- 서블릿 컨테이너
- JWT
- kafka
- Dead Letter Queue
- 지연 로딩
- Spring Data JPA
- dockerhub
- redis
- Spring
- @Transactional
- Routing Key
- AWS
- @ComponentScan
- MSA
- Spring Container
- mybatis
- 컨테이너
- DI
- CORS
- Web
- Today
- Total
look-forest
요약 - 검증과 예외 처리 본문
검증 (validation)
타임리프 스프링 검증 오류 통합 기능
BindingResult는 스프링이 제공하는 검증 오류를 보관하는 객체이다.
BindingResult 는 검증할 대상 바로 다음에 와야한다. BindingResult 는 Model에 자동으로 포함된다.
@ModelAttribute에 데이터 바인딩 오류 발생 시 오류 정보( FieldError )를 BindingResult 에 담아서 컨트롤러를 정상 호출
BindingResult에 검증 오류를 적용하는 3가지 방법
- @ModelAttribute의 객체에 타입 오류 등으로 바인딩 실패 시 스프링이 FieldError 생성해서 BindingResult에 넣어줌
- 개발자가 직접 넣어준다.
- Validator 사용
FieldError, ObjectError
- BindingResult에는 ObjectError, FieldError가 들어가는데, rejectedValue가 오류 발생시 사용자 입력 값을 저장하는 필드 타입 오류로 바인딩에 실패하면 스프링은 FieldError를 생성하면서 사용자가 입력한 값을 넣어둔다.
- codes, arguments 필드를 제공하여 오류 발생시 오류 코드로 메시지를 찾기 위해 사용한다. ( errors.properties )
- BindingResult 가 제공하는 rejectValue() , reject() 를 사용하면 FieldError , ObjectError 를 생성해준다.


스프링이 직접 만든 검증 오류 코드와 오류 메시지 처리도 커스텀 가능하다.
- 개발자가 직접 설정한 오류 코드 rejectValue() 를 직접 호출
- 스프링이 직접 검증 오류에 추가한 경우(주로 타입 정보가 맞지 않음)
스프링은 타입 오류가 발생하면 typeMismatch 라는 오류 코드를 사용한다
Validator 분리
- 목표 : 컨트롤러의 복잡한 검증 로직을 별도로 분리하자
- 방법 : Validator 구현 클래스를 만들고, 컨트롤러에서 주입받고, WebDataBinder에 validator를 추가한 후, validate 호출.
※ WebDataBinder
스프링의 파라미터 바인딩의 역할을 해주고 검증 기능도 내부에 포함한다.
WebDataBinder 에 검증기를 추가하면 해당 컨트롤러에서는 검증기를 자동으로 적용할 수 있다.
validator를 직접 호출하는 부분을 지우고, 대신에 검증 대상 앞에 @Validated를 붙여도 된다.

Bean Validation
특정 필드에 대한 검증 로직은 대부분 빈 값인 지 아닌지 등과 같이 매우 일반적인 로직으로, 정형화 할 수 있다.
검증 로직을 애노테이션을 활용해 모든 프로젝트에 적용할 수 있게 표준화 한 것이 바로 Bean Validation이다.
spring-boot-starter-validation 라이브러리를 넣으면 스프링부트가 자동으로 Bean Validator를 인지하고 스프링에 통합, 자동으로 글로벌 Validator로 등록한다.(LocalValidatorFactoryBean을 글로벌 Validator로 등록)
이 Validator는 빈 필드에 붙은 @NotNull 같은 애노테이션을 보고 검증을 수행
이렇게 글로벌 Validator가 적용되어 있기 때문에, @Valid , @Validated 만 적용하면 된다.
검증 오류가 발생하면, FieldError , ObjectError 를 생성해서 BindingResult 에 담아준다.
오류 코드가 애노테이션 이름으로 등록되므로 errors.properties에 오류코드를 등록해주면 된다.
오브젝트 오류(글로벌 오류)의 경우 오브젝트 오류 관련 부분만 직접 자바 코드로 작성하는 것을 권장한다.

Bean Validation 한계 - 수정시 검증 요구사항
동일한 모델 객체를 등록할 때와 수정할 때 각각 다르게 검증하는 방법은 아래 두가지가 있다.
- BeanValidation의 groups 기능을 사용한다. (실무에서는 주로 등록/수정용 폼 객체를 분리해서 사욯하므로 잘 사용x)
- Item을 직접 사용하지 않고, ItemSaveForm, ItemUpdateForm 같은 폼 전송을 위한 별도의 모델 객체를 만들어서 사용
Bean Validation - HTTP 메시지 컨버터
@Valid, @Validated 는 HttpMessageConverter (@RequestBody)에도 적용할 수 있다.

API의 경우 3가지 경우를 나누어 생각해야 한다.
- 성공 요청: 성공
- 실패 요청: JSON을 객체로 생성하는 것 자체가 실패함
- 검증 오류 요청: JSON을 객체로 생성하는 것은 성공했고, 검증에서 실패함
2. 실패 요청 응답 값 : HttpMessageConverter에서 요청 JSON을 ItemSaveForm 객체로 생성하는데 실패하는 경우
@RequestBody 는 HttpMessageConverter 단계에서 JSON 데이터를 객체로 변경하지 못하면 이후 단계 자체가 진행되지 않고 예외가 발생한다. 컨트롤러도 호출되지 않고, Validator도 적용할 수 없다

예외 처리와 오류 페이지
서블릿 예외 처리
- WAS(여기까지 전파) ← 필터 ← 서블릿 ← 인터셉터 ← 컨트롤러(예외발생)
- WAS(sendError 호출 기록 확인) ← 필터 ← 서블릿 ← 인터셉터 ← 컨트롤러 (response.sendError())
=> 에러 코드에 맞춰 tomcat이 기본으로 제공하는 오류 화면이 나타난다.
기본 오류 페이지 커스터마이징
1. 웹서버 커스터마이저 클래스를 만들고 오류 페이지 등록
2. 오류 페이지를 처리할 수 있는 컨트롤러 생성
필터의 경우 DispatcherType, 인터셉터는 excludePathPatterns를 활용하면 필터/인터셉터는 건너뛸 수 있다.
1. WAS(/error-ex, dispatchType=REQUEST) → 필터 → 서블릿 → 인터셉터 → 컨트롤러
2. WAS(여기까지 전파) ← 필터 ← 서블릿 ← 인터셉터 ← 컨트롤러(예외발생)
3. WAS 오류 페이지 확인
4. WAS(/error-page/500, dispatchType=ERROR) → 필터(x) → 서블릿 → 인터셉터(x) → 컨트롤러 → View
스프링 부트 - 오류 페이지
스프링 부트는 이런 과정을 모두 기본으로 제공한다.
1. ErrorPage 를 자동으로 등록한다. 이때 /error 라는 경로로 기본 오류 페이지를 설정한다.
2. BasicErrorController 라는 컨트롤러를 자동으로 등록한다.
따라서 개발자는 오류 페이지 화면만 BasicErrorController 가 제공하는 룰과 우선순위에 따라서
해당 경로 위치에 HTTP 상태 코드 이름의 뷰 파일을 넣어두면 된다!
BasicErrorController 의 처리 순서
- 뷰 템플릿
resources/templates/error/500.html
resources/templates/error/5xx.html - 정적 리소스( static , public )
resources/static/error/400.html
resources/static/error/404.html
resources/static/error/4xx.html - 적용 대상이 없을 때 뷰 이름( error )
resources/templates/error.html
API 예외 처리
스프링 부트가 제공하는 BasicErrorController 는 HTML 페이지를 제공하는 경우에는 매우 편리하다.
그런데 API 오류 처리는 형식이 매번 다를 수 있으므로, BasicErrorController 를 확장해서 사용해야만 한다.
이보다는 API 오류 처리는 뒤에서 설명할 @ExceptionHandler 를 사용하자.
HandlerExceptionResolver
예외가 발생해서 서블릿을 넘어 WAS까지 예외가 전달되면 HTTP 상태코드가 500으로 처리된다.
발생하는 예외에 따 라서 400, 404 등등 다른 상태코드로 처리하고 싶으면, HandlerExceptionResolver를 활용할 수 있다.
스프링 MVC는 컨트롤러(핸들러) 밖으로 예외가 던져진 경우 예외를 해결하고, 동작을 새로 정의할 수 있도록 HandlerExceptionResolver를 제공한다.

ExceptionResolver 활용
- 예외 상태 코드 변환 → response.sendError(400)
- 뷰 템플릿 / API 응답 처리 → BasicErrorController 없이 바로 응답

HandlerExceptionResolver 인터페이스를 직접 구현한 후 ExceptionResolver를 등록한 사례
ExceptionResolver 를 사용하면 컨트롤러에서 예외가 발생해도 ExceptionResolver 에서 예외를 처리해버린다.
따라서 예외가 발생해도 서블릿 컨테이너까지 예외가 전달되지 않고, 스프링 MVC에서 예외 처리는 끝이 난다.
결과적으로 WAS 입장에서는 정상 처리가 된 것이다. 이렇게 예외를 이곳에서 모두 처리할 수 있다는 것이 핵심이다.
그런데 HandlerExceptionResolver 를 직접 사용하기는 복잡하다. 특히 API 오류 응답의 경우 response 에 직접 데이터를 넣어야 해서 매우 번거롭고, ModelAndView 를 반환해야 하는 것도 API에는 잘 맞지 않는다.
스프링은 이 문제를 해결하기 위해 @ExceptionHandler 라는 매우 혁신적인 예외 처리 기능을 제공한다.
우선 스프링이 제공해주는 ExceptionResolver들을 알아보자.
API 예외 처리 - 스프링이 제공하는 ExceptionResolver
스프링 부트가 기본으로 제공하는 ExceptionResolver 는 다음과 같다.
HandlerExceptionResolverComposite 에 다음 순서로 등록
1. ExceptionHandlerExceptionResolver → @ExceptionHandler
2. ResponseStatusExceptionResolver → HTTP 응답 코드 변경 (response.sendError)
3. DefaultHandlerExceptionResolver → 스프링 내부 예외 처리 (eg.TypeMismatchException을 400으로 변경)
API 예외 처리 - @ExceptionHandler
스프링은 API 예외 처리 문제를 해결하기 위해 @ExceptionHandler 라는 애노테이션을 사용하는 매우 편리한 예외 처리 기능을 제공하는데, 이것이 바로 ExceptionHandlerExceptionResolver 이다.
스프링은 ExceptionHandlerExceptionResolver 를 기본으로 제공하고, 기본으로 제공하는 ExceptionResolver 중에 우선순위도 가장 높다. 실무에서 API 예외 처리는 대부분 이 기능을 사용한다

실행 흐름
- 컨트롤러를 호출한 결과 IllegalArgumentException 예외가 컨트롤러 밖으로 던져진다.
- 예외가 발생했으로 ExceptionResolver가 작동, 우선순위가 높은 ExceptionHandlerExceptionResolver가 실행.
- ExceptionHandlerExceptionResolver는 해당 컨트롤러에 IllegalArgumentException을 처리 할 수 있는 @ExceptionHandler가 있는지 확인.
- illegalExHandle() 를 실행한다. @RestController 이므로 illegalExHandle() 에도 @ResponseBody 가 적용.
따라서 HTTP 컨버터가 사용되고, 응답이 다음과 같은 JSON으로 반환된다. - @ResponseStatus(HttpStatus.BAD_REQUEST) 를 지정했으므로 HTTP 상태 코드 400으로 응답한다. (기본 200)
@ControllerAdvice
@ExceptionHandler 를 사용해서 예외를 깔끔하게 처리할 수 있게 되었지만, 정상 코드와 예외 처리 코드가 하나의 컨트롤러에 섞여 있다. @ControllerAdvice 또는 @RestControllerAdvice 를 사용하면 둘을 분리할 수 있다.

- @ControllerAdvice는 대상으로 지정한 여러 컨트롤러에 @ExceptionHandler, @InitBinder 기능을 부여
- @ControllerAdvice 에 대상을 지정하지 않으면 모든 컨트롤러에 적용된다. (글로벌 적용)
참고 자료 & 이미지 출처
스프링 MVC 2편 (김영한 님)
'Spring > Spring MVC - 웹 개발 활용 기술' 카테고리의 다른 글
| 스프링 타입 컨버터 (0) | 2025.01.24 |
|---|---|
| 요약 - 로그인 처리 (0) | 2024.08.25 |
| API 예외 처리 (0) | 2024.08.11 |
| 예외 처리와 오류 페이지 (0) | 2024.08.10 |
| 로그인 처리2 - 필터, 인터셉터 (0) | 2024.08.10 |