Bean Validation
- 특정 구현체가 아닌 Bean Validation2.0 (JSR-380)이라는 기술 표준이다.
- 여러 검증 애노테이션과 여러 인터페이스의 모음으로 구성되어 있다.
- 이러한 Bean Validation을 구현한 기술 중 일반적으로 사용하는 구현체는 하이버네이트 Validator다.
- Bean Validatior를 활용하면 애노테이션 기반으로 각종 구현 로직을 간단하게 적용할 수 있다.
- 공식 사이트
http://hibernate.org/validator/
The Bean Validation reference implementation. - Hibernate Validator
Express validation rules in a standardized way using annotation-based constraints and benefit from transparent integration with a wide variety of frameworks.
hibernate.org
- 공식 메뉴얼
https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/
Hibernate Validator 6.2.3.Final - Jakarta Bean Validation Reference Implementation: Reference Guide
Validating data is a common task that occurs throughout all application layers, from the presentation to the persistence layer. Often the same validation logic is implemented in each layer which is time consuming and error-prone. To avoid duplication of th
docs.jboss.org
- 검증 애노테이션 모음
Hibernate Validator 6.2.3.Final - Jakarta Bean Validation Reference Implementation: Reference Guide
Validating data is a common task that occurs throughout all application layers, from the presentation to the persistence layer. Often the same validation logic is implemented in each layer which is time consuming and error-prone. To avoid duplication of th
docs.jboss.org
🔍 Bean Validation 의존관계 추가
implementation 'org.springframework.boot:spring-boot-starter-validation'
- Bean Validation을 사용하기 위해선 build.gradle 파일에서 위의 코드를 추가해줘야 한다.
💡 Bean Validation 애노테이션 적용
- javax.validation 으로 시작하면 특정 구현에 관계없이 제공되는 표준 인터페이스다.
- org.hibernate.validator로 시작하면 하이버네이트 validator 구현체를 사용할 때만 제공되는 검증 기능이다.
- 실무에서 대부분 하이버네이트 validator를 사용한다.
📌 Item 객체
package hello.itemservice.domain.item;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class Item {
private Long id;
@NotBlank
private String itemName;
@NotNull
@Range(min=1_000, max=1_000_000)
private Integer price;
@NotNull
@Max(9_999)
private Integer quantity;
public Item() {
}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
- @NotBlank : 빈값 + 공백만 있는 경우 허용하지 않는다.
- @NotNull : null 값을 허용하지 않는다.
- @Range(min = 1000, max = 10000) : 범위 안의 값이어야 한다.
- @Max(9999) : 최대 9999까지만 허용한다.
💡 검증기 생성
- 스프링과 통합하면 우리가 직접 이런 검증기를 작성하지 않지만 이렇게 사용하는구나 정도만 참고할 것
검증기 생성
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
검증 실행
Set<ConstraintViolation<Item>> violations = validator.validate(item);
📌 테스트 코드 (검증기 직접 구현)
package hello.itemservice.validation;
import hello.itemservice.domain.item.Item;
import org.junit.jupiter.api.Test;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Set;
public class BeanValidationTest {
@Test
void beanValidation() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Item item = new Item();
item.setItemName(" "); // 공백
item.setPrice(0);
item.setQuantity(10000);
Set<ConstraintViolation<Item>> validate = validator.validate(item);
for (ConstraintViolation<Item> violation : validate) {
System.out.println("violation = " + violation);
System.out.println("violation.getMessage() = " + violation.getMessage());
}
}
}
violation = ConstraintViolationImpl{interpolatedMessage='1000에서 1000000 사이여야 합니다', propertyPath=price, rootBeanClass=class hello.itemservice.domain.item.Item, messageTemplate='{org.hibernate.validator.constraints.Range.message}'}
violation.getMessage() = 1000에서 1000000 사이여야 합니다
violation = ConstraintViolationImpl{interpolatedMessage='9999 이하여야 합니다', propertyPath=quantity, rootBeanClass=class hello.itemservice.domain.item.Item, messageTemplate='{javax.validation.constraints.Max.message}'}
violation.getMessage() = 9999 이하여야 합니다
violation = ConstraintViolationImpl{interpolatedMessage='공백일 수 없습니다', propertyPath=itemName, rootBeanClass=class hello.itemservice.domain.item.Item, messageTemplate='{javax.validation.constraints.NotBlank.message}'}
violation.getMessage() = 공백일 수 없습니다
- 오류 메시지는 하이버네이트가 직접 생성해준다.
Bean Validation - 스프링 적용
- 스프링 부트가 spring-boot-starter-validation 라이브러리를 추가하면 자동으로 Bean Validator로 등록한다.
- LocalValidatorFactoryBean이 글로벌 Validator로 등록되며 @NotNull 과 같은 애노테이션 검증을 수행한다.
- 이렇게 글로벌 Validator가 적용되어 있기 때문에 @Valid / @Validated 애노테이션만 적용하면 된다.
- 또한 검증 오류 발생시 FieldError, ObjectError 객체를 생성해서 BindingResult에 담는다.
- 스프링 부트 설정 클래스에서 임의로 글로벌 Validator를 등록하면 스프링 부트는 Bean Validator를 글로벌 Validator로 등록하지 않는다.
📌 Item (검증 객체)
package hello.itemservice.domain.item;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class Item {
private Long id;
@NotBlank
private String itemName;
@NotNull
@Range(min=1_000, max=1_000_000)
private Integer price;
@NotNull
@Max(9_999)
private Integer quantity;
public Item() {
}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
- 검증 받고자하는 필드에 @NotBalnk 와 같은 검증 애노테이션을 붙이면 된다.
📌 컨트롤러
@Controller
@Slf4j
@RequestMapping("/validation/v3/items")
@RequiredArgsConstructor
public class ValidationItemControllerV3 {
private final ItemRepository itemRepository;
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
// 검증 실패하면 다시 입력 폼으로
if(bindingResult.hasErrors()) {
log.info("errors={}", bindingResult);
return "validation/v3/addForm";
}
// 검증 성공 로직
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/validation/v3/items/{itemId}";
}
}
- 검증 객체에 @Validated 검증 애노테이션을 붙여주고 검증 결과를 담기 위해 BindingResult 클래스를 바로 다음 위치에 매개변수로 설정한다.
🔍 검증 순서
- @ModelAttribute 애노테이션이 붙은 객체의 각각의 필드에 타입 변환을 시도한다.
- 타입 변환 성공시, 다음 필드 진행
- 타입 변환 실패시, typeMismatch로 FieldError 객체가 생성된다.
- Validator 적용
💡 바인딩에 성공한 필드만 Bean Validation이 적용된다.
- Bean Validator는 바인딩에 실패한 필드는 Bean Validation을 적용하지 않는다.
- 모델 객체에 바인딩 받는 값이 정상으로 들어와야 검증의 의미가 있기 때문이다.
예)
- itemName에 문자 "A" 값이 들어온다. → 타입 변환 성공 → itemName 필드에 Bean Validation이 적용된다.
- price에 문자 "A" 값이 들어온다. → "A"를 숫자 타입 변환 실패 → typeMismatch FieldError 추가 → price 필드는 Bean Validation이 적용되지 않는다.
Bean Validation - 에러 코드
- Bean Validation을 적용하면 bindingResult에 등록된 검증 오류 코드가 검증 애노테이션 이름으로 등록된다. (MessageCodesResolver 사용)
예)
- @NotBlank
- NotBlank.item.itemName
- NotBlank.itemName
- NotBlank.java.lang.String
- NotBlank
- @Range
- Range.item.price
- Range.price
- Range.java.lang.Integer
- Range
📌 errors.properties
#Bean Validation 추가
NotBlank={0} 공백X
Range={0}, {2} ~ {1} 허용
Max={0}, 최대 {1}
- 에러 메시지 등록
- {0} 값은 검증 객체의 필드 명이다.
- {1}, {2}, ... 값은 애노테이션 속성 값이다.
🔍 Bean Validation 메시지 찾는 순서
- 생성된 메시지 코드 순서대로 messageSource에서 메시지를 찾는다.
- 매칭되는 메시지가 없으면 애노테이션의 message 속성을 통해 기본 메시지가 나온다.
- 예) @NotBlank(message = "공백! {0}")
Bean Validation - 오브젝트 오류
- @ScriptAssert() 애노테이션을 사용하면 Bean Validation에서 특정 필드(FieldError)가 아닌 오브젝트 관련 오류(ObjectError)를 처리할 수 있다.
📌 검증 객체 (Item)
package hello.itemservice.domain.item;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import org.hibernate.validator.constraints.ScriptAssert;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
@ScriptAssert(lang="javascript", script = "_this.price * this.quantity >= 10000", message = "총합 10000원 넘게 입력해주세요.")
public class Item {
private Long id;
@NotBlank
private String itemName;
@NotNull
@Range(min=1_000, max=1_000_000)
private Integer price;
@NotNull
@Max(9_999)
private Integer quantity;
public Item() {
}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
- 다음과 같은 순서로 메시지 코드를 찾는다.
- ScriptAssert.item
- ScriptAssert
🔍 @ScriptAssert 문제점
- 실제 사용해보면 제약이 많고 복잡하다.
- 실무에서는 검증 기능이 해당 객체의 범위를 넘어서는 경우가 종종 생기는데 그 때 마다 대응하기가 어렵다.
- 그러므로 @ScriptAssert 애노테이션을 억지로 사용하는 것 보다 오브젝트 오류 관련 부분만 직접 자바 코드로 작성하는 것을 권한다.
🔍 오브젝트 오류 사용 방법 - 직접 자바 코드 구현
- @ScripteAssert() 애노테이션을 쓰기보단 직접 자바 코드로 구현할 것.
📌 컨트롤러
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
// 특정 필드가 아닌 복합 룰 검증
if(item.getPrice()!=null && item.getQuantity()!=null) {
int resultPrice = item.getPrice() * item.getQuantity();
if(resultPrice<10_000) {
bindingResult.reject("totalPriceMin", new Object[]{10_000, resultPrice}, null);
}
}
...
}
- 이처럼 검증 객체에 대한 부분은 직접 자바 코드로 작성하는 것이 더 좋다.
- 만약 코드 중복이 걱정된다면 따로 메서드로 분리하면 된다.
🧷 정리
- 간단한 필드 검증에는 Bean Validation을 이용하여 검증 애노테이션( @Validated )을 이용한다.
- 복잡한 객체 검증에는 제약이 많은 애노테이션( @ScriptAssert() ) 애노테이션을 이용하기 보단 자바 코드로 직접 구현한다.
- 코드 재사용성이 높다면 모듈화 할 것.
Bean Validation의 한계
🔍 한계점
- 검증 조건을 등록할 때와 수정할 때를 서로 다르게 적용할 수가 없다.
- 예) 등록할 때는 최대 수량을 9999가지 설정하고, 수정할 때는 최대 수량 제한 해제
🔍 문제 해결
- 동일한 모델 객체를 상황에 따라 각각 다르게 검증하는 방법은 2가지가 있다.
- Bean Validation의 groups 기능 사용
- 사용이 복잡하기 때문에 실무에서 잘 안쓰인다.
- Item 객체를 직접 사용하지 않고, ItemSaveForm, ItemUpdateForm 같은 폼 전송을 위한 별도의 모델 객체를 만들어서 사용
- Bean Validation의 groups 기능 사용
💡 Bean Validation의 groups 기능 사용
- @Validated 애노테이션은 groups 속성 기능을 지원한다. ( @Valid 애노테이션은 groups 기능이 없다.)
- 등록시에 검증할 기능과 수정시 검증할 기능을 각각 그룹으로 나눠 적용한다.
- groups 기능을 사용하면 검증 객체의 코드가 많이 복잡해진다. 그러므로 실무에서 잘 쓰이지 않는다.
- 실무에서는 두 번째 방법인 등록용 폼 객체와 수정용 폼 객체를 분리해서 사용한다.
📌 저장용 groups
package hello.itemservice.domain.item;
public interface SaveCheck {
}
📌 수정용 groups
package hello.itemservice.domain.item;
public interface UpdateCheck {
}
📌 검증 객체 (Item)
package hello.itemservice.domain.item;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.sql.Savepoint;
@Data
//@ScriptAssert(lang="javascript", script = "_this.price * this.quantity >= 10000", message = "총합 10000원 넘게 입력해주세요.")
public class Item {
@NotNull(groups = UpdateCheck.class) // 수정 요구 사항 추가
private Long id;
@NotBlank(groups = {SaveCheck.class, UpdateCheck.class})
private String itemName;
@NotNull(groups = {SaveCheck.class, UpdateCheck.class})
@Range(min=1_000, max=1_000_000, groups = {Savepoint.class, UpdateCheck.class})
private Integer price;
@NotNull(groups = {Savepoint.class, UpdateCheck.class})
@Max(value = 9_999, groups = SaveCheck.class)
private Integer quantity;
public Item() {
}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
- groups는 다수의 그룹도 설정할 수 있으며 필요에 따라 맞는 그룹을 선택해 검증할 수 있다.
📌 등록 컨트롤러
@PostMapping("/add")
public String addItemV2(@Validated(SaveCheck.class) @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
// 특정 필드가 아닌 복합 룰 검증
if(item.getPrice()!=null && item.getQuantity()!=null) {
int resultPrice = item.getPrice() * item.getQuantity();
if(resultPrice<10_000) {
bindingResult.reject("totalPriceMin", new Object[]{10_000, resultPrice}, null);
}
}
// 검증 실패하면 다시 입력 폼으로
if(bindingResult.hasErrors()) {
log.info("errors={}", bindingResult);
return "validation/v3/addForm";
}
// 검증 성공 로직
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/validation/v3/items/{itemId}";
}
- @Validated 애노테이션에 속성으로 SaveChack.class를 사용했다.
- Item 객체는 각각 @Validated 애노테이션에 작성된 인터페이스가 선언된 검증만 수행한다.
📌 수정 컨트롤러
@PostMapping("/{itemId}/edit")
public String editV2(@PathVariable Long itemId, @Validated(UpdateCheck.class) @ModelAttribute Item item, BindingResult bindingResult) {
// 특정 필드가 아닌 복합 룰 검증
if(item.getPrice()!=null && item.getQuantity()!=null) {
int resultPrice = item.getPrice() * item.getQuantity();
if(resultPrice<10_000) {
bindingResult.reject("totalPriceMin", new Object[]{10_000, resultPrice}, null);
}
}
if (bindingResult.hasErrors()) {
log.info("errors={}", bindingResult);
return "validation/v3/editForm";
}
itemRepository.update(itemId, item);
return "redirect:/validation/v3/items/{itemId}";
}
- @Validated 애노테이션에 속성으로 UpdateCheck.class를 사용했다.
- Item 객체는 각각 @Validated 애노테이션에 작성된 인터페이스가 선언된 검증만 수행한다
💡 Form 전송 객체 분리
- 상품 등록과 상품 수정시 사용자와 주고 받을 전용 폼 전달 객체를 만들어서 사용한다.
- 상황에 맞는 전용 폼객체를 따로 만들어서 상황에 맞게 검증을 하고, 전송 객체이기에 사용자에게 노출해도 상관 없는 객체가 된다.
- 물론 이렇게 구현할 경우 도메인 객체를 한 번 더 변환을 해야하는 추가 과정이 생기지만 별도의 폼 객첼르 만들기 때문에 검증이 중복되지 않는다.
- 사용 에) HTML Form → ItemSaveForm → Controller → Item 생성 → Repository
📌 검증 객체 (Item)
package hello.itemservice.domain.item;
import lombok.Data;
@Data
public class Item {
private Long id;
private String itemName;
private Integer price;
private Integer quantity;
public Item() {
}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
📌 저장 검증 객체
package hello.itemservice.web.validation.form;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class ItemSaveForm {
@NotBlank
private String itemName;
@NotNull
@Range(min = 1000, max = 1_000_000)
private Integer price;
@NotNull
@Max(9999)
private Integer quantity;
}
📌 수정 검증 객체
package hello.itemservice.web.validation.form;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class ItemUpdateForm {
@NotNull
private Long id;
@NotBlank
private String itemName;
@NotNull
@Range(min = 1000, max = 1_000_000)
private Integer price;
// 수정에서는 수량은 자유롭게 변경 가능
private Integer quantity;
}
📌 저장 컨트롤러
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute("item") ItemSaveForm form, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
// 특정 필드가 아닌 복합 룰 검증
if(form.getPrice()!=null && form.getQuantity()!=null) {
int resultPrice = form.getPrice() * form.getQuantity();
if(resultPrice<10_000) {
bindingResult.reject("totalPriceMin", new Object[]{10_000, resultPrice}, null);
}
}
// 검증 실패하면 다시 입력 폼으로
if(bindingResult.hasErrors()) {
log.info("errors={}", bindingResult);
return "validation/v4/addForm";
}
// 검증 성공 로직
// 폼 객체의 데이터를 기반으로 Item 객체를 생성한다. (변환 과정)
Item item = new Item(form.getItemName(),
form.getPrice(),
form.getQuantity());
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/validation/v4/items/{itemId}";
}
- Item 검증 객체 대신 ItemSaveForm을 전달 받는다.
- 그리고 @Validated 로 검증도 수행하고 검증 결과를 BindingResult로 받는다.
- @ModelAttribute("item") 에 item 이름을 넣어준 부분을 주의할 것.
- item을 적지 않으면 ItemSaveForm의 규칙에 의해 itemSaveForm이라는 이름으로 MVC Model에 담기게 된다. 이렇게 되면 뷰 템플릿에서 접근하는 th:object 이름도 함께 변경해야한다.
📌 수정 컨트롤러
@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId, @Validated @ModelAttribute("item") ItemUpdateForm form, BindingResult bindingResult) {
// 특정 필드가 아닌 복합 룰 검증
if(form.getPrice()!=null && form.getQuantity()!=null) {
int resultPrice = form.getPrice() * form.getQuantity();
if(resultPrice<10_000) {
bindingResult.reject("totalPriceMin", new Object[]{10_000, resultPrice}, null);
}
}
if (bindingResult.hasErrors()) {
log.info("errors={}", bindingResult);
return "validation/v4/editForm";
}
// 폼 객체의 데이터를 기반으로 Item 객체를 생성한다. (변환 과정)
Item itemParam = new Item(form.getItemName(),
form.getPrice(),
form.getQuantity());
itemRepository.update(itemId, itemParam);
return "redirect:/validation/v4/items/{itemId}";
}
- Item 검증 객체 대신 ItemUpdateForm을 전달 받는다.
- 그리고 @Validated 로 검증도 수행하고 검증 결과를 BindingResult로 받는다.
- @ModelAttribute("item") 에 item 이름을 넣어준 부분을 주의할 것.
- item을 적지 않으면 ItemSaveForm의 규칙에 의해 itemSaveForm이라는 이름으로 MVC Model에 담기게 된다. 이렇게 되면 뷰 템플릿에서 접근하는 th:object 이름도 함께 변경해야한다.
Bean Validation - HTTP 메시지 컨버터
- @Valid / @Validated 애노테이션은 HttpMessageConverter( @RequestBody ) 에도 적용할 수 있다.
- 참고
- @ModelAttirbute 는 HTTP 요청 파라미터(URL 쿼리 스트링, POST Form)를 다룰 때 사용된다.
- @RequestBody 는 HTTP Body의 데이터를 객체로 변환할 때 사용한다. 주로 API JSON 요청을 다룰 때 사용한다.
📌 컨트롤러
package hello.itemservice.web.validation;
import hello.itemservice.web.validation.form.ItemSaveForm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/validation/api/items")
public class ValidationItemApiController {
@PostMapping("/add")
public Object addItem(@RequestBody @Validated ItemSaveForm form, BindingResult bindingResult) {
log.info("API 컨트롤러 호출");
if (bindingResult.hasErrors()) {
log.info("검증 오류 발생 errors={}", bindingResult);
return bindingResult.getAllErrors();
}
log.info("성공 로직 실행");
return form;
}
}
테스트용 요청 정보 (POST MAN 프로그램으로 데이터 전송)
POST http://localhost:8080/validation/api/items/add
Context-Type : application/json
{"itemName":"hello", "price":1000, "quantity":10}
🔍 API 응답 3가지 경우
- 요청 성공
- JSON을 객체로 생성하는 것을 성공했고, 검증도 성공했다.
- 요청 실패
- JSON을 객체로 생성하는 것 자체를 실패했다
- 이 경우 HttpMessageConverter에서 요청 JSON을 객체로 생성하는 것 자체가 실패햇다.
- 지정한 객체(ex: Item)로 만들지 못했기 때문에 해당 요청 컨트롤러 호출도 되지 않고 Validator도 실행되지 않는다.
- 검증 오류 실패
- JSON을 객체로 생성하는 것은 성공했지만, 검증에서 실패했다.
- 이 경우 검증 실패 내역이 BindingResult 클래스에 들어있기 때문에 적절히 꺼내 담아 반환하면 된다.
💡 @ModelAttribute vs @RequestBody
- HTTP 요청 파라미터를 처리하는 @ModelAttribute 는 각각의 필드 단위로 세밀하게 적용된다. 그러므로 특정 필드가 타입이 불일치 하더라도 나머지 필드는 정상으로 처리할 수 있다. 그렇기 때문에 컨트롤러가 호출되고 나머지 필는 Validator가 실행 된다.
- HttpMessageConverter는 @ModelAttribute 와 다르게 필드 단위가 아닌 객체 전체 단위로 적용되기 때문에 메시지 컨버팅이 성공해서 객체가 만들어진 다음에 검증 애노테이션(ex : @Validated)이 적용된다. 그래서 타입 불일치일 경우 컨트롤러 호출 자체가 안되고 Validator가 실행되지 않는다.
- HttpMessageConverter 단계에서 실패하면 예외가 발생한다. 예외 발생시 원하는 모양으로 예외를 처리하는 방법은 나중에 예외 처리 방법에서 다룬다.
👀 참고자료
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의
웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있
www.inflearn.com
'[ Spring ] > SpringMVC 2편' 카테고리의 다른 글
[Spring] 로그인 처리2 - 필터, 인터셉터 (0) | 2022.03.13 |
---|---|
[Spring] 로그인 처리1 - 쿠키, 세션 (0) | 2022.03.12 |
[Spring] 검증1 - Validation (0) | 2022.03.07 |
[Spring] 메시지, 국제화 (0) | 2022.03.06 |
[Spring] 타임리프 - 스프링 통합과 폼 (0) | 2022.03.05 |