[유효성검증 #3] 유효성 검증 아키텍처 설계
2025. 6. 9. 22:34ㆍ토이프로젝트/유효성검사 (Validation)
전체 아키텍처 흐름도 이미지

Spring AOP 기반 유효성 검증 처리 구조 정리
Spring에서 DTO 유효성 검증을 처리할 때, 기본 제공되는 BeanValidation만으로는 부족한 경우가 있다.
이럴 때 커스텀 Validator를 추가로 적용할 수 있도록 구조를 설계하며, 두 가지 방식을 함께 활용한다.
- ✅ BeanValidation + CustomValidation을 함께 사용하도록 구성한다.
- ✅ 유효성 검증을 AOP로 감싸 공통 포맷으로 처리하도록 만든다.
- ✅ 어노테이션(@CustomValidate)을 통해 자동으로 Validator를 스캔하고 매핑한다.
전체 흐름 개요
Controller (with @CheckValidation)
↓
ValidationAspect (AOP)
├─ BeanValidation 실행
└─ CustomValidation 실행
└─ DTO에 @CustomValidate가 붙어 있을 경우 해당 Validator 연결
🧩 핵심 구성 요소
1. @CheckValidation 어노테이션
Controller에서 유효성 검증 처리를 위한 어노테이션
(AOP의 trigger 역할)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckValidation {}
2. AOP - ValidationAspect
유효성검증 공통 처리를 위한 AOP
1. Bean Validation을 먼저 수행
2. Custom Validation을 수행 (getValidator()를 이용한 등록된 커스텀 검증 클래스를 가져옴)
@Around("@annotation(CheckValidation)")
public Object validationAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
for (Object arg : joinPoint.getArgs()) {
// 1. BeanValidation 수행
springValidator.validate(arg);
// 2. CustomValidator 수행
Validator validator = validFactory.getValidator(arg.getClass());
if (validator != null) {
DataBinder binder = new DataBinder(arg);
binder.addValidators(validator);
binder.validate();
}
}
return joinPoint.proceed();
}
3. @CustomValidate 어노테이션
DTO 클래스에 이 어노테이션을 붙이면 커스텀 Validator 대상으로 인식된다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomValidate {}
4. ValidateFactoryImpl - Validator 자동 등록
해당 패키지 하위에 있는, @CustomValidate 붙은 커스텀 유효섬 검증 클래스를 등록
(scanValidateTarget() 5번 코드 참고)
public ValidateFactoryImpl(List<Validator> validators) {
Set<Class<?>> targets = scanValidateTarget("패키지명");
for (Validator v : validators) {
if (isSpringDefault(v)) continue;
for (Class<?> dto : targets) {
if (v.supports(dto)) {
map.put(dto, v);
}
}
}
}
5. @CustomValidate 스캔 메서드
ClassPathScanningCandidateComponentProvider를 이용해 DTO (@CustomValidate) 를 스캔한다.
( Custom 유효성 검증 클래스를 자동 등록하기 위한 Annotation )
private Set<Class<?>> scanValidateTarget(String basePackage) {
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(CustomValidate.class));
for (BeanDefinition bd : scanner.findCandidateComponents(basePackage)) {
results.add(Class.forName(bd.getBeanClassName()));
}
return results;
}
Controller 예시
@CheckValidation
@PostMapping("/post/member")
public ResponseEntity<?> postMember(@RequestBody MemberDto dto) {
return ResponseEntity.ok().build();
}
컨트롤러에서는 단순히 @CheckValidation만 붙이면, 내부적으로 모든 유효성 검사가 자동 수행된다.
장점
1. 다양한 DTO마다 다른 유효성 검증이 필요한 경우
- 예: 회원가입 DTO, 주문 DTO, 주소 DTO 등에서 각각 다른 검증 로직이 필요할 때
- @CustomValidate + Validator 조합으로 DTO별 맞춤 검증 로직을 적용할 수 있다.
2. 기본 BeanValidation으로는 한계가 있는 경우
- 예: 한 필드만 판단해서는 안 되고, 다른 필드와 조합해 유효성을 판단해야 하는 경우 (ex. startDate < endDate)
- Validator 내부에서 객체 전체를 보고 로직 작성이 가능하다.
3. 유효성 검증을 공통 포맷으로 통일하고 싶은 경우
- AOP로 감싸기 때문에 유효성 실패 응답도 일관되게 처리할 수 있다.
- 에러 응답 포맷을 커스터마이징하고, 프론트엔드와 사전 약속된 형태로 맞출 수 있다.
4. 유효성 로직이 컨트롤러를 지저분하게 만드는 것이 싫을 때
- @CheckValidation 하나로 유효성 검증이 끝나므로, 컨트롤러는 핵심 로직에 집중할 수 있다.
주의할 점
| 런타임 스캔 비용 | @CustomValidate 스캔은 클래스 수가 많을수록 비용이 발생함 (앱 초기 구동 시) (해결 : 스캔할 영역을 최대한 제한 -> DTO를 모아두는 패키지에서만 스캔 적용) |
| 동적 등록의 복잡성 | 프레임워크 초심자 입장에서는 구조를 이해하는 데 진입장벽이 있음 (해결 : 아키텍처 문서 필요) |
| AOP 사용 | AOP를 사용하므로, 디버깅 시 흐름을 추적하기가 다소 어려움 (특히 유효성 오류가 잘못 매핑될 경우) ( 해결 : AOP 내부 진입 로그 (JoinPoint 진입 클래스 로깅) + DTO 로그 + 예외 발생 전후 로깅 필요 ) |
정리
1. BeanValidation과 CustomValidation을 함께 적용할 수 있도록 설계한다.
(Bean Validation도 같이 사용하므로 Field 단위 어노테이션도 사용 가능)
2. AOP로 유효성 검증을 감싸 컨트롤러 로직을 간결하게 유지한다.
3. @CustomValidate를 통해 DTO에 대응하는 Validator를 자동 등록한다.
확장 아이디어
1. @ValidPhone, @ValidName 같은 필드 단위 어노테이션 추가
2. @Validated(Group.class) 등 그룹 유효성 검증 도입
3. 다국어 메시지 처리를 위한 messages.properties 연동
'토이프로젝트 > 유효성검사 (Validation)' 카테고리의 다른 글
| [유효성검증 #2] Bean Validation를 이용한 유효성 검증 (0) | 2025.06.06 |
|---|---|
| [유효성검증 #1] Validator + BindingResult를 이용한 유효성 검증 (1) | 2025.06.06 |