서버를 개발 할 때 클라이언트의 입력 값에 따른 유효성 검사는 필수적으로 일어난다.
코드로 유효성 검사를 해도 좋지만 API를 새로 만들 때 마다. 비슷한 유효성 검사 코드를 추가하는건 너무 비효율 적이다.
이런 문제점을 해결하기 위해 Java는 데이터 유효성 검사 표준 기술인 Java Bean Validation을 제공한다.
Java Bean Validation는 Java 클래스의 필드, 또는 메서드에 적용할 수 있는 어노테이션들을 제공 한다. 해당 어노테이션들은 필드 또는 속성의 값으로 충족해야 하는 조건을 정의한다.
자주 사용하는 어노테이션들은 다음과 같다.
유저 생성 API를 만든다고 가정하고 Request DTO에 Java Bean Validation에서 제공하는 기본 constraint 어노테이션을 적용하였다.
public class CreateUserRequest {
@NotBlank(message = "닉네임에 공백이 포함될 수 없습니다.")
private String nickname;
@NotBlank(message = "이메일이 공백일 수 없습니다.")
@Email
private String email;
@NotNull(message = "생년월일이 공백일 수 없습니다.")
@PastOrPresent
private LocalDate birthday;
@NotBlank(message = "비밀번호가 공백일 수 없습니다.")
private String password;
}
위 코드를 간단히 설명 하자면 nickname은 null,””,” “ 허용하지 않고,email은 이메일 형식만 허용하고
birthday는 null과 현재시간 보다 전의 시간대만 가능하다.
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/user")
public class UserController {
private final UserService userService;
@PostMapping
public CreateUserResponse createUser(@RequestBody @Valid CreateUserRequest request) throws Exception {
return userService.createUser(request);
}
}
Dto에 constraint 어노테이션만 붙히면 validation이 발생하지 않는다. 유효성 검사가 필요한 Controller나 Service단에 @Valid 어노테이션을 사용해야 한다.
@Valid가 붙은 객체에 있는 constraint 어노테이션으로 유효성 검사를 한다.
custom validation으로 닉네임에 특수문자를 허용하지 않는 어노테이션을 만들어 보자.
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = NicknameValidator.class)
public @interface Nickname {
String message() default "닉네임엔 특수문자를 사용할수 없습니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
우선 @interface로 생성할 어노테이션을 정의합니다. @Constraint 어노테이션을 명시해줘 해당 생성할 어노테이션이 validation 어노테이션이라는것을 명시해줍니다. @Constraint 안에 validatedBy속성으로는 vaidation로직이 들어갈 클래스 명을 정의합니다.
public class NicknameValidator implements ConstraintValidator<Nickname,String> {
@Override
public booleanisValid(String value, ConstraintValidatorContext context) {
if(value ==null) {
return false;
}
Pattern pattern = Pattern.compile("!\"#[$]%&\\(\\)\\{\\}@`[*]:[+];-.<>,\\^~|'\\[\\]");
return !pattern.matcher(value).find();
}
}
위의 코드에서 NicknameValidator 클래스는 ConstraintValidator<Nickname , String> 인터페이스를 구현합니다. 이때 첫 번째 제네릭 타입은 유효성 검사 어노테이션 클래스를, 두 번째 제네릭 타입은 검사할 값의 타입을 지정합니다. 이 예제에서는 String 타입의 값을 검사합니다.
pattern에 특수문자를 찾는 정규식을 설정한다음 특수문자가 있으면 false 없으면 true를 반환하게
정의 하였다.
public class CreateUserRequest {
@NotBlank(message = "닉네임에 공백이 포함될 수 없습니다.")
@NickName
private String nickname;
@NotBlank(message = "이메일이 공백일 수 없습니다.")
@Email
private String email;
@NotNull(message = "생년월일이 공백일 수 없습니다.")
@PastOrPresent
private LocalDate birthday;
@NotBlank(message = "비밀번호가 공백일 수 없습니다.")
private String password;
}
Request DTO의 nickname필드에 @NickName 어노테이션을 선언 하여 특수문자를 허용하지 않게 하였다.