본문 바로가기
강의&책 리뷰/스프링 MVC 2편 - 백엔드 웹 개발 활용 기술

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 / 섹션4. 검증1 - Validation (3)

by chansungs 2024. 2. 7.
728x90
반응형

안녕하세요.

인프런 김영한 님의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 섹션 4 검증 - Validation 리뷰입니다.

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


 

Vaildator 분리 1

복잡한 검증로직을 별도로 분리하자

 

 

 

ItemValidator 생성

package hello.itemservice.web.validation;

import hello.itemservice.domain.item.Item;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

@Slf4j
@Component
public class ItemValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return Item.class.isAssignableFrom(clazz);
        // item == clazz
        // item -- subItem 자식클래스
    }

    @Override
    public void validate(Object target, Errors errors) {
        Item item = (Item) target;

        // 아래의 bindingResult.rejectValue 같다 , 단순한기능만 제공한다.
        // ValidationUtils.rejectIfEmptyOrWhitespace(bindingResult, "itemName", required);

        // 검증 로적
        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() >= 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) {
//                bindingResult.addError(new ObjectError("item",new String[]{"totalPriceMin"},new Object[]{10000, resultPrice}, null));
                errors.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
            }
        }




    }
}

 

 

addItemV4를 복사해서 addItemV5 추가

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("bindingResult error ==> {}", 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` 를 스프링 빈으로 주입 받아서 직접 호출했다.

 

실행

실행해보면 기존과 완전히 동일하게 동작하는 것을 확인할 수 있다. 검증과 관련된 부분이 깔끔하게 분리되었다.

 

 

Vaildator 분리 2

스프링이 `Validator` 인터페이스를 별도로 제공하는 이유는 체계적으로 검증 기능을 도입하기 위해서다. 그런데 앞 에서는 검증기를 직접 불러서 사용했고, 이렇게 사용해도 된다. 그런데 `Validator` 인터페이스를 사용해서 검증기를 만들면 스프링의 추가적인 도움을 받을 수 있다.

 

WebDataBinder를 통해서 사용하기
`
WebDataBinder` 는 스프링의 파라미터 바인딩의 역할을 해주고 검증 기능도 내부에 포함한다.

너무 깊게 알 필요는 없다.

 

ValidationItemControllerV2 에 추가

    /**
     * ValidationItemControllerV2 으로 실행되었을때 아래의 메소드들이 실행되면 실행될 메소드마다
     * init를 실행한다.
     * */
    @InitBinder
    public void init(WebDataBinder dataBinder) {
        dataBinder.addValidators(itemValidator);
    }

 

 

이렇게 `WebDataBinder` 에 검증기를 추가하면 해당 컨트롤러에서는 검증기를 자동으로 적용할 수 있다.

`@InitBinder` 해당 컨트롤러에만 영향을 준다. 글로벌 설정은 별도로 해야한다.

 

    @PostMapping("/add")
    public String addItemV6(@Validated @ModelAttribute Item item, BindingResult bindingResult, // 순서 중요
                            RedirectAttributes redirectAttributes,
                            Model model
    ) {

        // 검증에 실패하면 다시 입력 폼으로 이동
        if (bindingResult.hasErrors()) {
            log.info("bindingResult error ==> {}", 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}";
    }

 

 

동작 방식
`
@Validated` 는 검증기를 실행하라는 애노테이션이다.

이 애노테이션이 붙으면 앞서 `WebDataBinder` 에 등록한 검증기를 찾아서 실행한다. 그런데 여러 검증기를 등록한다 면 그 중에 어떤 검증기가 실행되어야 할지 구분이 필요하다. 이때 `supports()` 가 사용된다. 여기서는

`supports(Item.class)` 호출되고, 결과가 `true` 이므로 `ItemValidator` `validate()` 가 호출된다.

 

 

global 적용

package hello.itemservice;

import hello.itemservice.web.validation.ItemValidator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@SpringBootApplication
public class ItemServiceApplication implements WebMvcConfigurer {

	public static void main(String[] args) {
		SpringApplication.run(ItemServiceApplication.class, args);
	}

	public ItemServiceApplication getInstance() {
		return new ItemValidator();
	}

}

 

 

참고

검증시 `@Validated` `@Valid` 둘다 사용가능하다.
`javax.validation.@Valid` 를 사용하려면 `build.gradle` 의존관계 추가가 필요하다. `implementation 'org.springframework.boot:spring-boot-starter-validation'` `@Validated` 는 스프링 전용 검증 애노테이션이고, `@Valid` 는 자바 표준 검증 애노테이션이다.

728x90
반응형