Back-End/Spring

[Spring MVC2][검증(Validation)] - 컨트롤러와 검증의 분리

얄루몬 2022. 5. 2. 17:39

💻본 포스팅은 '스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 김영한'님의 강의를 듣고 작성되었습니다.

https://inf.run/vQHp

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com


[목차]
0. 들어가기에 앞서..

1. Validator 분리(1)
2. Validator 분리(2)

[0. 들어가기에 앞서..]

컨트롤러와 검증을 분리하는 이유가 무엇일까?

  • 컨트롤러 안에 검증 로직이 차지하는 부분이 많아지게 되면 분리해서 사용하는 것이 바람직하다.
  • 분리하지 않고 컨트롤러 안에서 수많은 검증 로직을 사용하면 컨트롤러가 너무 많은 역할을 차지하게 되기 때문에 분리해 사용하는 것이 좋다.
  • 또한 분리한 검징 로직은 재사용할 수도 있게 된다.
    • 따로 클래스를 사용해서 validation 관련 코드를 몰아넣어주자.

 

[1. Validator 분리(1)]

package hello.itemservice.web.validation;

import hello.itemservice.domain.item.Item;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

@ComponentScan
public class ItemValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return Item.class.isAssignableFrom(clazz);
        //자식 클래스까지 전부 커버 가능.
    }

    @Override
    public void validate(Object target, Errors errors) {
        //검증 로직
        //Object로 넘어오기 때문에 casting을 해주어야 한다.
        Item item = (Item) target;


        //특정 필드 예외
        if (!StringUtils.hasText(item.getItemName())) {
            errors.rejectValue("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() > 10000) {
            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);
            }
        }
    }
}
  • 컨트롤러가 아닌 따로 클래스를 생성해서 그 안에 넣어준 뒤, 컨트롤러에서 불러 사용하는 방식으로 검증을 진행하고 있다.
  • 이때 validator 인터페이스를 상속받아 구현해서 사용하는 것이 핵심이다.

 

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

        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}";
    }
  • itemValidator.validate(item,bindingResult); -검증 클래스 직접사용 
private final ItemValidator itemValidator;

 

  • 의존성 주입도 잊지말고 해주자.
  • 따로 위의 방법대로 하지 않고 필요할 때마다 객체생성해서 진행해도 되지만 싱글톤이 보장되지 않기때문에 비추.

 

[2. Validator 분리(2)]

    @PostMapping("/add")
    public String addItemV6(@Validated @ModelAttribute Item item, BindingResult bindingResult,
                            RedirectAttributes redirectAttributes) {

        //검증에 실패하게 될 때 다시 입력 폼으로 간다.
        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}";
    }
  • 직접 검증 클래스를 호출해서 사용하는 것이 아닌 @Valiated 애너테이션을 검증 대상 앞에 붙여 사용하는 방식으로 기존 방식과 동일하게 동작한다.
  • @Validated
    • 검증기를 실행하라는 애노테이션이다.
    • 이 애노테이션이 붙으면 WebDataBinder에 등록한 검증기를 찾아 실행한다.
    • 여러 검증기를 등록할 땐?
      • supports()를 사용해 어떤 검증기를 실행할지를 구분한다.
      • supports(Item.class) 호출 -> 결과가 true -> ItemValidator의 validate()가 호출된다.