스프링 프레임워크에서는 주로 MVC 컨트롤러를 통해 데이터의 유효성을 검사합니다. 하지만 모든 데이터가 MVC 컨트롤러를 통해 처리되는 것은 아니며, 다양한 상황에서 데이터 유효성 검사가 필요할 수 있습니다. 이러한 경우를 위해 스프링은 다음과 같은 두 가지 방법을 제공합니다:
@Validated 어노테이션을 사용한 메서드 파라미터 검증
직접 Validator 사용하기
때에따라 적합 한 방식이 다른데, 저는 다른 곳에서 API 를 통해 받은 data 의 유효성을 검증하기 위해 2번 방식을 택했습니다. 2번을 택한 이유는, API 통신을 전담하는 DAO 안에서 유효성 검사까지 진행하였기 때문입니다.
하지만 Validator 를 통해 직접 Custom ConstraintValidator 의 유효성 검사를 진행하였을때 의존성 주입이 되지 않는 오류가 발생 하였고 해당 문제를 해결한 과정을 남깁니다.
유효성 검사를 할때 사용했던 Validator 을 가지고 있는 Class 입니다. (오류 출력을 위해 출력 함수들을 더한 상태입니다)
Validator 로 Validation.buildDefaultValidatorFactory().getValidator()
를 사용하고 있습니다.
해당 함수의 반환 객체는 Hibernate Validator 입니다.
해당 ConstraintValidator 는 List 를 받아서 내부에 유효성 검사가 실패한 element 들을 삭제하고 반환하는 역할을 합니다. 이때 내부에서 Validator 을 한번 더 사용하기 때문에 기본으로 등록 된 Hibernate Validator 를 @Autowired 로 주입 받아서 사용하였습니다.
위에서는 Validator 를 주입 받지 않고 getValidator() 한 이유?
처음에는 Util class 로 사용하기 위해 validate 함수도 static 으로 만들었습니다.
하지만 테스트 코드를 위해 주입 식으로 변경하던중 해당 코드는 변경 하지 못하고 남겨져 버렸습니다.
(대충 실수했다는 뜻)
ConstraintValidator 속 Validator 가 null 로 제대로 주입을 받지 못하는 에러가 납니다.
ValidationUtil 속 Vlaidator 은 org.hibernate.validator.internal.engine.ValidatorImpl
인 Hibernate Validator 구현체로 제대로 들어가 있습니다.
그렇다면 왜 Custom ConstraintValidator 속 Validator 만 제대로 주입이 되지 않는 걸까요?
이유는 ValidationUtil 속 Validator 를 Spring 에 의해 주입 받지 않고, 임의로 가져다 사용했기 때문입니다.
ConstraintValidator 가 Spring의 의존성 주입 기능을 사용하여 다른 빈을 주입받아야 하는데 호출 된 Validator 가 Spring 에 의해 관리 되지 않는다면 문제가 생깁니다. 수동으로 생성된 Validator는 Spring의 라이프사이클과 의존성 주입 메커니즘과 독립적으로 작동하기 때문에, 필요한 의존성이 주입되지 않기 때문입니다.
따라서, ConstraintValidator가 다른 Spring 빈에 의존하고 있다면, Spring에 의해 관리되는 Validator를 사용해야 합니다.
만약 ValidationUtil 속 Validator 를 Spring 에 의해 주입 받는 다면, Spring이 Validator와 그에 연결된 모든 ConstraintValidator들에 필요한 의존성을 자동으로 주입할 것입니다.
ConstraintValidator 구현체의 생명주기
보통 Bean Validation 프레임워크에 의해 생성되고 초기화됩니다.
일반적인 스프링 빈의 생명주기와는 다름.
Spring boot 를 사용해 Validation 을 진행 한다면, Spring이 관리하는 Validator 구현체는 LocalValidatorFactoryBean
이 될 것 입니다.
그 이유는 Spring 이 Validation 을 진행하는 과정에 대한 글에서 한번 다루었다.
그렇다면 의존성 주입 말고, 바로 LocalValidatorFactoryBean
을 가져다 사용해도 될까요?
LocalValidatorFactoryBean
가 Bean Validation 과 Spring 을 통합해 주는 역할을 하니 가능할지도 모른다는 생각이 듭니다.
이런 식으로 new 로 새로 만들어서 사용해 보았습니다.
원래 new 로 만들면 Spring ApplicationContext 와 연관이 없게 생성되는 것이라 문제가 생기지만, 테스트를 위해 한번 이렇게 진행해 보았습니다.
No target Validator set
에러가 납니다.
해당 오류 메시지는 SpringValidatorAdapter에서 유효성 검증을 수행하기 위한 Validator가 설정되지 않았을 때 발생합니다. 이 오류는 주로 Spring의 ValidationUtil 클래스 또는 유사한 유효성 검증 유틸리티 클래스에서 Spring이 관리하는 Validator를 사용하지 않고, 대신 직접 생성한 Validator 인스턴스를 사용할 때 발생합니다. 해당 Validator는 Spring의 ApplicationContext와 연동되어 있지 않아서, Spring이 관리하는 Validator 인스턴스를 주입받아야만 Spring의 유효성 검증 메커니즘과 완전히 통합된 Validator 을 사용할 수 있습니다.
결국 생성자 주입으로 Validator 을 주입 받았습니다.
이렇게 하자 ValidationUtil
과 ConstraintValidator
속 Validator 가 LocalValidatorFactoryBean
으로 잘 들어가 있고 작동도 잘합니다!
만약 ConstraintValidator 내부에서 스프링 빈을 주입받을 일이 없다면 Spring 에 의해 관리 받는 Validator 을 사용하지 않아도 되지만, Spring 생태계 속에서 외부 기술을 사용할때는 언제 어디서 통합에 대한 문제가 발생할지 모르기때문에 Spring 제공 버전을 사용하시는 것을 추천 드립니다!
ref
https://www.latera.kr/blog/2019-07-23-test-custom-validation/
https://medium.com/@gaemi/java-%EC%99%80-spring-%EC%9D%98-validation-b5191a113f5c
잘 보고 갑니다!