4. 검증1 - Validation

ChoiJaewoo·2026년 2월 4일

Spring MVC 2편

목록 보기
4/10

4. 검증1 - Validation

검증 요구사항

요구사항

  1. 타입 검증 - 가격, 수량에 문자가 들어오면 검증 오류 처리
  2. 필드 검증 - 상품명 : 공백x, 가격 : 1000원 이상, 1백만원 이하, 수량 : 최대 9999
  3. 특정 필드의 범위를 넘어서는 검증 - 가격 * 수량의 합은 10,000원 이상

현재 : 지금 잘못된 입력이 들어오면, 400에러 : Bad Request 가 발생한다.

개선 : 잘못된 값이 들어오면, 잘못입력되었습니다 등의 어떤 오류가 발생하였는지 사용자에게 알려주어야 한다.

  • 참고 : 클라이언트 검증, 서버 검증
    • 클라이언트 검증은 조작할 수 있어, 보안에 취약하다.
    • 서버만으로 검증하면, 즉각적인 고객 사용성이 부족해진다.
    • 둘을 적절히 섞더라도, 최종적으로 서버 검증은 필수이다.
    • API 방식을 사용하면, API 스펙을 잘 정의하여 검증 오류를 API 응답 결과에 잘 넘겨주어야 한다.

상품 저장 성공 Case

상품 저장 실패 - 검증 실패 Case

요구사항 정리

  1. 상품 등록 폼에서 받아온 model 에서 상품 저장 로직을 실행하는데, 이때 검증을 해야 한다.
  2. 검증에 실패한다면, 유저에게 다시 상품 등록 폼을 보여줘야 한다.
  3. 유저는 이전 자신이 입력했던 정보 중 올바른 정보는 그대로 보여주고, 가격이 잘못 입력되었다면, 가격을 다시 입력해주세요 라는 문구가 보여야 한다.
  4. 이를 위해 검증 로직이 실패하면, 기존 받았던 model 데이터와 검증 오류 결과를 함께 포함하여 다시 상품 등록 폼에게 전달해야 한다.

프로젝트 V1

상품 등록 검증

ValidationItemControllerV1.class

@PostMapping("/add")  //실제 저장 로직 실행
    public String addItem(@ModelAttribute Item item, RedirectAttributes redirectAttributes, Model model) {
        // 검증 오류 결과를 보관
        Map<String, String> errors = new HashMap<>();
        //검증 로직
        if (isNull(item)) {
            errors.put("itemName", "상품 이름은 필수 입니다");
        }
        if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
            errors.put("price", "가격은 1,000 ~ 1,000,000 까지 허용합니다");
        }
        if (item.getQuantity() == null || item.getQuantity() > 9999) {
            errors.put("quantity", "수량은 최대 9,999까지 허용합니다");
        }
        if (item.getPrice() != null && item.getQuantity() != null) {
            int resultPrice = item.getPrice() * item.getQuantity();
            if (resultPrice < 10000) {
                errors.put("globalError", "가격 * 수량의 합은 10,000 원 이상이여야 합니다. 현재 값 : " + resultPrice);
            }
        }
        //검증에 싪패하면 다시 입력 폼으로
        if (hasError(errors)) {
            log.info("errors = {}", errors);
            model.addAttribute("errors", errors);
            return "validation/v1/addForm";
        }

        //검증에 성공한 이후 저장 로직
        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId", savedItem.getId());
        redirectAttributes.addAttribute("status", true);
        return "redirect:/validation/v1/items/{itemId}";
    }

    private static boolean isNull(Item item) {
        return !StringUtils.hasText(item.getItemName());
    }

    private static boolean hasError(Map<String, String> errors) {
        return !errors.isEmpty();
    }
  • isNull, hasError → 가독성을 높이기 위해 메서드로 뽑아냄. (단축키 option + command + m)
th:class="${errors?.containsKey('itemName')} ? 'form-control field-error' : 'form-control'"

만약, 상품 목록에서 상품 등록 버튼을 눌러 http://localhost:8080/validation/v1/items/add 요청을 보냈다면,

errors Hashmap 객체를 만들지 않는다 (Get - /add) 만약 이때 ? 를 붙이지 않는다면, NullPointException이 발생한다. 따라서 clasappend 를 사용하여 nulll point excepiton 을 방지한다.

  • 가격 (Integer) 에 text (String) 이나 Double , Long 타입의 입력이 들어오면 검증에 실패하여 에러가 발생한다. → 해결 과제 1
  • item 의 price 에 문자를 입력하는 것 처럼 타입 오류가 발생해도 고객이 입력한 문자를 화면에 남겨야 한다. 하지만 컨트롤러가 호출 되더라도 Item의 price는 Integer이므로 문자를 보관할 수 없다. 따라서 문자가 바인딩이 불가하여 고객이 입력한 문자가 사라지게 되고, 고객은 본인이 어떤 내용을 입력하여 오류가 발생했는지 모른다. → 결국 잘못 입력된 값도 어딘가에 저장되어야 한다.

프로젝트 V2

Tip💡 페이지에서 replace : 단축키 command + R

Tip💡 디렉토리, 패키지 선택하고, 선택한 하위 모든 페이지에서 replace : 단축키 command + Shift + R

V1 → V2 생성.

BindingResult 1

ValidationItemControllerV2

@PostMapping("/add")  //실제 저장 로직 실행
public String addItem(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {
	...    
}

→ @ModelAttribute 뒤에 BindingResult 를 추가한다.

→ 오류가 생기면 BindingResult 에 담는다. 이것이 V1 의 errors와 같은 역할을 한다.

//검증 로직
if (isNull(item)) {
    bindingResult.addError(new FieldError("item", "itemName", "상품 이름은 필수 입니다"));
}

오류의 종류 :

  1. 특정 Field 에 대한 오류 : FieldError
  2. Global 오류 : ObjectError

FieldError는 2가지 생성자를 제공하고, 그중 첫번째는 3가지 파라미터가 있다.

  1. objectName : item → 모델 attribute에 담기는 이름
  2. field : itemName → item 의 itemName
  3. default message → “상품 이름은 필수 입니다”

ObjectError는 2가지 파라미터가 있다.

  1. object명 : item → 모델 attribute에 담기는 이름
  2. message → "가격 * 수량의 합은 10,000 원 이상이여야 합니다. 현재 값 : " + resultPrice

이후, 검증에 실패 시 입력 폼으로 return 해야 한다.

//검증에 싪패하면 다시 입력 폼으로
if (bindingResult.hasErrors()) {
    log.info("errors = {}", bindingResult);
    return "validation/v2/addForm";
}
  • 단, model.addAttribute 하지 않아도 됨. 왜? → Binding result는 자동으로 View에 같이 넘어간다.

BindingResult 2

  • BindingResult : 스프링이 제공하는 검증 오류를 보관하는 객체이다. 검증 오류가 발생하면 여기에 보관하면 된다.
  • BindingResult 가 있으면 @ModelAttribute 에 데이터 바인딩 시 오류가 발생해도 컨트롤러가 호출된다!

만약, @ModelAttribure에 바인딩 시 타입 오류가 발생하면?

  • BindingResult 가 없으면 → 400 오류가 발생하면서 컨트롤러가 호출되지 않고, 오류 페이지로 이동한다.
  • BindingResult 가 있으면 → 오류 정보 (FieldError) 를 BindingResult 에 담아서 컨트롤러를 정상 호출한다.

FieldError, ObjectError

ValidationItemControllerV2.class - addItemV2

@PostMapping("/add")  //실제 저장 로직 실행
public String addItemV2(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {
    //검증 로직
    if (isNull(item)) {
        bindingResult.addError(new FieldError("item", "itemName", item.getItemName(),false,null,null, "상품 이름은 필수 입니다"));
    }
    if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
        bindingResult.addError(new FieldError("item", "price", item.getPrice(), false, null, null, "가격은 1,000 ~ 1,000,000 까지 허용합니다"));
    }
    if (item.getQuantity() == null || item.getQuantity() > 9999) {
        bindingResult.addError(new FieldError("item", "quantity", item.getQuantity(), false, null, null, "수량은 최대 9,999까지 허용합니다"));
    }
    if (item.getPrice() != null && item.getQuantity() != null) {
        int resultPrice = item.getPrice() * item.getQuantity();
        if (resultPrice < 10000) {
            bindingResult.addError(new ObjectError("item", null, null, "가격 * 수량의 합은 10,000 원 이상이여야 합니다. 현재 값 : " + resultPrice));
        }
    }
    //검증에 싪패하면 다시 입력 폼으로
    if (bindingResult.hasErrors()) {
        log.info("errors = {}", bindingResult);
        return "validation/v2/addForm";
    }

    //검증에 성공한 이후 저장 로직
    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("status", true);
    return "redirect:/validation/v2/items/{itemId}";
    }

FieldError

  • FieldError 는 두가지 생성자를 제공하고, 그 두번째 생성자
    //위의 BindingResult1 에서 사용한 생성자
    public FieldError(String objectName, 
    									String field, 
    									String defaultMessage);
    									
    //현재 BindingResult2 에서 사용한 생성자									
    public FieldError(String objectName,
    									String field, 
    									@Nullable Object rejectedValue, 
    									boolean bindingFailure, 
    									@Nullable String[] codes, 
    									@Nullable Object[] arguments, 
    									@Nullable String defaultMessage)
    • objectName : 오류가 발생한 객체 이름
    • field : 오류 필드
    • rejectedValue : 사용자가 입력한 값 (= 거절된 값)
    • bindingFailure : 타입 오류 같은 바인딩 실패인지? 아니면 검증 실패인지? - 바인딩 실패 : true, 검증 실패 : false
    • codes : 메시지 코드
    • arguments : 메시지에 사용되는 인자
    • defaultMessage : 기본 오류 메시지
  • 위의 생성자를 사용한 FieldError 처리
    bindingResult.addError(new FieldError("item", "itemName", item.getItemName(),false,null,null, "상품 이름은 필수 입니다"));
  • 타임 리프에서 사용자 입력 값을 유지
    • th:field = “*{price}” 는 정상 상황일 때, 모델 객체의 값을 사용하고, 오류가 발생 시 FieldError에서 보관한 값을 사용해서 값을 출력한다.

→ Failed to convert property value of type java.lang.String to required type java.lang.Integer for property price; nested exception is java.lang.NumberFormatException: For input string: "qqq”
: 스프링이 기본으로 제공해주는 오류 메시지이다.

하지만, 불친절하고 개발자스럽다,,, 일반 사용자에게 친절하지 않다. → 오류 메시지를 관리하는 메커니즘을 공부해보자!

오류 코드와 메시지 처리 1

  • messages 처리를 위해 기본으로 등록된 messages 이외에 errors 를 application.properties 에 추가한다.
  • src/main/resources/errors.properties 를 생성 후 아래 내용을 추가한다.
    required.item.itemName=상품 이름은 필수입니다.
    range.item.price=가격은 {0} ~ {1} 까지 허용합니다.
    max.item.quantity=수량은 최대 {0} 까지 허용합니다.
    totalPriceMin=가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}

→ error_en.properties 파일을 생성하여 오류메시지도 국제화 처리가 가능하다.

이제 errors 에 등록한 메시지를 사용해보자.

ValidationItemControllerV2.class - addItemV3() 추가

@PostMapping("/add")  //실제 저장 로직 실행
public String addItemV3(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {
    //검증 로직
    if (isNull(item)) {
        bindingResult.addError(new FieldError("item", "itemName", item.getItemName(), false, new String[]{"required.item.itemName"}, null, null));
    }
    if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
        bindingResult.addError(new FieldError("item", "price", item.getPrice(), false, new String[]{"range.item.price"}, new Object[]{1000, 1000000}, null));
    }
    if (item.getQuantity() == null || item.getQuantity() > 9999) {
        bindingResult.addError(new FieldError("item", "quantity", item.getQuantity(), false, new String[]{"max.item.quantity"}, new Object[]{9999}, null));
    }
    if (item.getPrice() != null && item.getQuantity() != null) {
        int resultPrice = item.getPrice() * item.getQuantity();
        if (resultPrice < 10000) {
            bindingResult.addError(new ObjectError("item", new String[]{"totalPriceMin"}, new Object[]{10000, resultPrice}, null));
        }
    }
    //검증에 싪패하면 다시 입력 폼으로
    if (bindingResult.hasErrors()) {
        log.info("errors = {}", bindingResult);
        return "validation/v2/addForm";
    }

    //검증에 성공한 이후 저장 로직
    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("status", true);
    return "redirect:/validation/v2/items/{itemId}";
}
  • codes : nullString[]{”…”, ”…”} : errors.properties 에서 오류메시지를 찾는다, 없으면 defaultMessage 출력됨.
  • arguments : nullObject[]{ … , … } : 오류메시지에 넘길 인자를 넣어줄 수 있다.

오류 코드와 메시지 처리2

  • FieldError, ObjectError 의 인자를 하나하나 넣어야한다 → 다루기가 어렵고, 번거롭다.

  • 오류 코드도 좀 더 자동화 할 수 있을까??

    → Controller에서 BindingResult는 검증해야 할 객체 바로 다음에 온다. 따라서 BindingResult는 이미 본인이 검증해야 할 객체를 알고 있다.

rejectValue(), reject()

BindingResult 는 rejectValue(), reject() 를 제공한다. 이를 사용하면 , FieldError 와 ObjectError 를 직접 생성하지 않고, 깔끔하게 오류를 다룰 수 있다.

addItemV3 (Before)

bindingResult.addError(new FieldError("item", "itemName", item.getItemName(), false, new String[]{"required.item.itemName"}, null, null));
bindingResult.addError(new FieldError("item", "price", item.getPrice(), false, new String[]{"range.item.price"}, new Object[]{1000, 1000000}, null));
bindingResult.addError(new FieldError("item", "quantity", item.getQuantity(), false, new String[]{"max.item.quantity"}, new Object[]{9999}, null));
bindingResult.addError(new ObjectError("item", new String[]{"totalPriceMin"}, new Object[]{10000, resultPrice}, null));

addItemV4 (After)

bindingResult.rejectValue("itemName", "required"); 
bindingResult.rejectValue("price", "range", new Object[]{1000, 1000000}, null);
bindingResult.rejectValue("quantity", "max", new Object[]{9999}, null);
bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);

rejectValue() 는 3가지 방법을 지원한다.

  1. void rejectValue*(*@Nullable String field, String errorCode*)*;
  2. void rejectValue*(*@Nullable String field, String errorCode, String defaultMessage*)*;
  3. void rejectValue*(*@Nullable String field, String errorCode, @Nullable Object*[]* errorArgs, @Nullable String defaultMessage*)*;

reject() 도 3가지 방법을 지원한다.

  1. void reject*(*String errorCode*)*;
  2. void reject*(*String errorCode, String defaultMessage*)*;
  3. void reject*(*String errorCode, @Nullable Object*[]* errorArgs, @Nullable String defaultMessage*)*;

분명 errors.properties에 errorCode 를
required.item.itemName=상품 이름은 필수입니다.

라고 하였다.

하지만, rejectValue의 errorCode 로 넘겨준 값은 “required”, “range”, “max”, “totalPriceMin” 이다.

어떻게 required → required.item.itemName 으로 찾을까??

MessageCodesResolver 를 통해 찾는다!

오류 코드와 메시지 처리 3

기존 만들었던 오류 메시지는 상세히, 자세히 만들었다.

required.item.itemName=상품 이름은 필수입니다.
range.item.price=가격은 {0} ~ {1} 까지 허용합니다.
max.item.quantity=수량은 최대 {0} 까지 허용합니다.
totalPriceMin=가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}

하지만, 범용성을 갖춰 단순하게 만들 수도 있다.

required=필수 값 입니다.
range=범위는 {0} ~ {1} 까지 허용합니다.
max=최대 {0} 까지 허용합니다.

하지만, 메시지를 세밀하게 작성하기 어렵다.

만약, 위의 두개가 동시에 작성되어 있다면, bindingResult.rejectValue("itemName", "required"); 은 어떤것을 고르게 될까?

→ 객체명과 필드명을 조합한 세밀한 메시지 코드가 있다면, 해당 메시지 (=세밀한 메시지) 를 높은 우선순위로 사용한다.

오류 코드와 메시지 처리 4

Test

    @Test
    void messageCodesResolverObject() {
        String[] messageCodes = codesResolver.resolveMessageCodes("required", "item");
        for (String messageCode : messageCodes) {
            System.out.println("messageCode = " + messageCode);
        }
    }

실행 결과

messageCode = required.item
messageCode = required

→ String[] 을 만들어주는 것을 알 수 있다. 그리고 우선 순위는 디테일 한 것이 먼저이다.

Test

@Test
    void messageCodesResolverField() {
        String[] messageCodes = codesResolver.resolveMessageCodes("required", "item", "itemName", String.class);
        for (String messageCode : messageCodes) {
            System.out.println("messageCode = " + messageCode);
        }
    }

실행 결과

messageCode = required.item.itemName
messageCode = required.itemName
messageCode = required.java.lang.String
messageCode = required

오류 코드와 메시지 처리 5

errors.properties 추가

Level1 ~ 4까지 추가.

#Level1
required.item.itemName
...
#Level2
...
#Level3
required.java.lang.String
...
#Level4
required
  • 각각 레벨을 주석 처리하며 어떤 메시지를 사용하게 되는지 알 수 있다.

ValidationUtils

간단하게 사용할 수 있음. 기능은 Empty 또는 공백 인 경우만 검증할 수 있다.

//Before
if (isNull(item)) {
  bindingResult.rejectValue("itemName", "required");
}

//After
ValidationUtils.rejectIfEmptyOrWhitespace(bindingResult, "itemName", "required");

오류 코드와 메시지 처리 6

검증 오류 코드는 2가지로 나눌 수 있다.

  1. 개발자가 직접 설정한 오류 코드 → rejectValue() 를 직접 호출해야 한다.
  2. 스프링이 직접 검증 오류에 추가한 경우 (예시 : 타입이 맞지 않는 경우 “typeMismatch”

  • 가격에 Integer type 이 아닌, 다른 자료형이 입력되었을 때, 에러 로그를 출력한 결과이다.

    codes → String[]

    • codes[0] : typeMismatch.item.price
    • codes[1] : typeMismatch.price
    • codes[2] : typeMismatch.java.lang.Integer
    • codes[3] : typeMismatch
  • default message : default message [Failed to convert property value of type 'java.lang.String' to required type 'java.lang.Integer' for property 'price'; nested exception is java.lang.NumberFormatException: For input string: "price!!!”

앞에서의 errors.properties에 메시지를 추가하여 우선 순위에 따른 에러 메시지를 출력했다.

그것과 같은 방법으로, 위 4개의 코드이름과 같은 이름으로 내가 원하는 메시지를 추가하면 내가 설정한 메시지를 사용할 수 있다.

결과적으로 소스코드를 하나도 건들지 않고, 원하는 메시지를 단계별로 설정할 수 있다!

Validator 분리 1

현재까지는 검증 로직이 Controller 에 붙어 있고, 이것의 비중이 매우 크다.

즉, Controller는 핵심 로직이 아닌 다른 기능을 많이 하는 중이다.

이것을 별도의 검증 역할 클래스로 분리하자!

ItemValidator.class ← implement Validator.interface

@Slf4j
@Component
public class ItemValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return Item.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        Item item = (Item) target;
        //검증 로직
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "itemName", "required");

        if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
            errors.rejectValue("price", "range", new Object[]{1000, 1000000}, null);
        }
        if (item.getQuantity() == null || item.getQuantity() > 9999) {
            errors.rejectValue("quantity", "max", new Object[]{9999}, null);
        }
        if (item.getPrice() != null && item.getQuantity() != null) {
            int resultPrice = item.getPrice() * item.getQuantity();
            if (resultPrice < 10000) {
                errors.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
            }
        }
    }
}

ValidationItemControllerV2.class 추가 및 수정

private final ItemValidator itemValidator; //추가

@PostMapping("/add")  //수정
public String addItemV5(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {

    itemValidator.validate(item, bindingResult);
    //검증에 싪패하면 다시 입력 폼으로
    if (bindingResult.hasErrors()) {
        log.info("errors = {}", bindingResult);
        return "validation/v2/addForm";
    }

    //검증에 성공한 이후 저장 로직
    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("status", true);
    return "redirect:/validation/v2/items/{itemId}";
    }

Tip💡

@RequiredArgsConstructor ↔ 생성자 + @Autowired

: 생성자가 하나일 때는 Autowired 생략 가능.

왜 이렇게 쓸까?? → 싱글톤 방식으로 사용하지 않으면, 객체를 계속 생성해서 사용해야 한다.

private final ItemValidator itemValidator;
...
itemValidator.validate(item, bindingResult); //싱글톤 방식 
->
new NoSpringBeanItemValidator().validate(item, bindingResult);

///

public class NoSpringBeanItemValidator{

    public void validate(Object target, Errors errors) {
        Item item = (Item) target;
        //동일한 검증 로직
        ...
        }
	  }
}

가능은 하다만,,, 요청에 대한 객체가 반복되어 생성된다.

다음 단계에서 supports() , validate(Object target, Errors errors) 가 무엇인지 다음 단계에서 알아보자.

Validator 분리 2

WebDataBinder : 스프링 MVC 내부에서 스프링의 파라미터 바인딩 역할을 해주고, 검증 기능도 내부에 포함한다.

이것을 사용하기 위해 애노테이션 @InitBinder 를 붙여준다.

그러면, 애노테이션 @InitBinder 붙은 매서드는 언제 호출이 되는가?

: 이 컨트롤러가 호출 될 때 마다 (해당 컨트롤러로 요청이 올 때 마다) 항상 불려져서 WebDataBinder 가 새로 만들어지고, 검증기가 내부에 들어있다.

이러한 이유로, 당연히 컨트롤러 내부에서만 사용 가능하다!

addItemV6

@PostMapping("/add")  //실제 저장 로직 실행
public String addItemV6(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {
    //검증에 싪패하면 다시 입력 폼으로
    if (bindingResult.hasErrors()) {
        log.info("errors = {}", bindingResult);
        return "validation/v2/addForm";
    }

    //검증에 성공한 이후 저장 로직
    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("status", true);
    return "redirect:/validation/v2/items/{itemId}";
}

→ 검증 하고자 하는 ModelAttribute 앞에 @Validated 애노테이션을 붙여준다.

@Validated

  • 검증기를 실행하라는 애노테이션이다.
  • 해당 애노테이션이 붙으면 앞서 WebDataBinder 에 등록한 검증기를 찾아서 실행한다.
  • 만약, WebDataBinder 에 여러 검증기가 등록되었다면? (로그인, 수정, 등등의 다른 상황별 검증기)
  • 어떤 검증기를 실행해야 할지 구분해야 한다! → supports() 가 사용된다!
        @Override
        public boolean supports(Class<?> clazz) {
            return Item.class.isAssignableFrom(clazz);
        }
    • supports(Item.class) 가 호출이 되고, supports(Item.class) return 값이 true 가 된다. 따라서 해당 요청에서는 ItemValidator가 사용되어 ItemValidator.validate() 가 호출될 수 있다.

참고 : @Validated는 스프링 전용 검증 애노테이션이고, @Valid 는 자바 표준 검증 애노테이션이다. 둘 다 사용 가능하긴 하다. 하지만, @Valid 를 사용하기 위해서는 build.gradle dependency 추가가 필요함.

0개의 댓글