Validation
문제
- 데이터 유효성 검사 로직의 문제점
1. 애플리케이션 전체에 분산되어 있다.
2. 코드 중복이 심하다.
3. 비즈니스 로직에 섞여 있어, 검사 로직 추적이 어렵고 애플리케이션이 복잡해진다.
해결 방법
- Java에서 Bean Validation이라는 데이터 유효성 검사 프레임워크를 제공한다.
- 위 문제들을 해결하기 위해 다양한 제약(Contraint)을 도메인 모델(Domain Model)에
어노테이션(Annotaion)으로 정의할 수 있게 한다.
- 유효성 검사가 필요한 객체에 직접 정의하는 방법으로 기존 유효성 검사 로직의 문제점을 해결한다.
제약 검사 설정과 기능
- Validation Starter를 추가한다.
- Service나 Bean에서 사용하기 위해서는 @Validate와 @Valid를 추가해야 한다.
- @Valid가 설정된 메소드가 호출될 때 유효성 검사를 진행한다.
- Controller에서는 @Validated가 필요 없이 검사가 필요한 곳에 @Valid를 추가하면 된다.
- Bean Validation 에서는 @Length, @NotBlank, @NotNull 등을 이용하여 설정할 수 있다.
public class CreateContact{
@Length(max=64)
@NotBlank
private String uid;
@NotNull
private ContactType contactType;
@Length(max=1_600)
private String contact;
}
- 유효성 검사 진행 시 같은 데이터를 여러 번 실행하게 될 경우 애플리케이션 성능에 영향을 미칠 수 있기 때문에 주의해야 한다.
- Bean Validation에서 제공하는 제약이 아닌, 필요한 제약을 직접 정의해서 사용할 수 있다.
출처 : https://meetup.toast.com/posts/223
Custom Annotation을 이용하여 유효성 체크
1. 커스텀 어노테이션 정의
2. ConstraintValidator를 구현한 클래스 선언
- ConstraintValidator<커스텀 어노테이션, 검증할 객체>
3. 해당 객체에 @Valid 붙여주기 & 해당 객체 안에서 검증할 필드에 커스텀 어노테이션 작성하기
Custom Annotation 정의
@Documented
@Constraint(validatedBy = PasswordValidation.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Password {
// 유효성 체크 시 잘못된 경우 보여줄 메시지
String message() default "암호 오류";
// 유효성 검사기 어떠한 상황에서 이루어져야 하는지 정의
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int min() default 8;
int max() default 12;
}
@Documented
- javadoc으로 api 문서 생성 시 설명도 포함하도록 지정하기 위해 선언
@Constraint
- 인터페이스 ConstraintValidator를 구현한 객체를 지정한다.
- 해당 객체를 검증한다는 것을 표기한다.
@Target
- 어노테이션을 어느 곳에 적용할 것인지 결정
- default는 모든 곳이다.
- 인스턴스 변수에만 적용 → ElementType.FIELD
- 클래스, 인터페이스, enum에만 적용 → ElementType.TYPE
- 메소드에만 적용 → ElementType.METHOD
- 등이 존재한다.
@Retention
- 어노테이션이 언제까지 유지될 것인지 결정.
- RetentionPolicy.SOURCE
: 컴파일 후 정보들이 사라진다. @Override, @SuppressWarnings 등
- RetentionPolicy.CLASS
1. default 값.
2. 컴파일 타임 까지만 존재하기 때문에 .class 파일에는 존재하지만, 런타임시 없어진다
3. Reflection 사용이 불가능하다.
- RetentionPolicy.RUNTIME
1. 런타임시 까지 존재한다.
2. 커스텀 어노테이션 만들 때 주로 사용한다.
3. Reflection 사용이 가능하다.
2. ConstraintValidator 구현 클래스
public class PasswordValidation implements ConstraintValidator<Password,String> {
private int min;
private int max;
@Override
public void initialize(Password constraintAnnotation) {
min=constraintAnnotation.min();
max=constraintAnnotation.max();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
int passwordLength = value.length();
return (!value.isEmpty() && min <= passwordLength && passwordLength <= max);
}
}
- ConstraintValidator<어노테이션 이름, 검증할 객체>
- initialize 메소드를 오버라이딩 하여 초기화를 진행한다.
- isValid 메소드를 오버라이딩 하여 실제 객체를 검증한다.
- true인 경우 정상 객체
- false인 경우 비정상 객체
3. 검증
@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping
public ResponseEntity<ResponseDto> createUser(@RequestBody @Valid UserRequestDto requestDto) throws UserPresentException {
return userService.createUser(requestDto);
}
}
- @Valid가 붙은 객체에 있는 값들의 유효성을 체크한다.
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserRequestDto {
@NotBlank
private String id;
@NotBlank
private String name;
@Password
private String password;
@NotBlank
private String phoneNumber;
}
- @Valid 객체 안 값들 중 유효성 체크가 필요한 부분에 커스텀 어노테이션을 붙인다.
4. 예외 처리
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler({MethodArgumentNotValidException.class})
public ResponseEntity<ResponseDto> notValidException(MethodArgumentNotValidException ex){
ResponseDto responseDto = ResponseDto.builder()
.message(ex.getBindingResult().getAllErrors().get(0).getDefaultMessage())
.build();
return new ResponseEntity<>(responseDto, HttpStatus.BAD_REQUEST);
}
}
- @Valid가 붙은 객체에 있는 값들의 유효성에서 문제가 있는 경우 MethodArgumentNotValidException 예외가 발생한다.
- getBindingResult().getAllErrors().get(0).getDefaultMessage()를 통해 Custom Annotation의 message를 가져올 수 있다.
'Spring > Spring 팁' 카테고리의 다른 글
Kotlin과 all-open (0) | 2022.02.14 |
---|---|
Kotlin과 Spring Bean (0) | 2022.02.06 |
[Spring] @Configuration과 @Component, 그리고 @Bean (0) | 2022.01.29 |
[ Spring Security ] Spring Security0 (2) | 2021.08.30 |
댓글