끄적끄적
[spring] @Valid 활용하기 본문
학습한 내용을 정리한 것입니다.
생략된 내용들과 부정확할 수 있는 내용들에 주의해주세요.
- DTO 검증하기
@PutMapping("/board/{boardId}")
public ResponseEntity updatePost(@PathVariable Long boardId,
@Valid BoardDTO.Update dto,
@ModelAttribute("accountDTO") AccountSecurityDTO accountDTO){
boardService.updatePost(boardId, dto, accountDTO);
return ResponseEntity.ok(Result.SUCCESS);
}
@Data
public static class Update{
@NotBlank(message = "please, enter \"title\"")
@Size(min=1,max=50,message = "title must be at least 1 characters and at most 50 characters.")
private String title;
@NotBlank(message = "please, enter \"content\"")
@Size(min=1,max=350,message = "content must be at least 1 characters and at most 350 characters.")
private String content;
@Valid
private List<FileDTO> existFileInfoList;
@FileSize(fileSize = 1024*1024, nullable = true, message = "The maximum file size is 1MB.")
@FileExtension(fileExtension = {"txt","hwp","png","jpg"}, nullable = true, message = "not allow extension.")
private List<MultipartFile> inputFileList;
}
- DTO 필드간의 유효성을 검증하기
- ex) 비밀번호, 비밀번호 확인에 대한 비교
- @Target이 ElementType.TYPE 점에 유의
@Data
@SignUpPassword
public static class SignUp{
@NotBlank(message = "please, enter \"password\"")
@Pattern(regexp ="^(?=.*[A-Za-z])(?=.*\\d)(?=.*[$@$!%*#?&])[A-Za-z\\d$@$!%*#?&]{8,}$"
,message = "Passwords must be at least 8 characters in length, including English, numbers and special characters.")//영문,숫자,특문 8글자 이상
private String password;
@NotBlank(message = "please, enter \"password check \"")
private String passwordCheck;
//중략...
@Documented
@Constraint(validatedBy = SignUpPasswordValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SignUpPassword {
String message() default "not same password and password-check";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Component
@RequiredArgsConstructor
public class SignUpPasswordValidator implements ConstraintValidator<SignUpPassword, AccountDTO.SignUp> {
@Override
public boolean isValid(AccountDTO.SignUp dto, ConstraintValidatorContext cxt) {
String password = dto.getPassword();
String passwordCheck = dto.getPasswordCheck();
return password.equals(passwordCheck);
}
}
- @Valid 어노테이션에 매개변수를 사용해서 더 명시적으로 사용하기
참고 : http://dolszewski.com/java/multiple-field-validation/
@EqualFields(baseField = "password", matchField = "confirmedPassword")
public class UserSignUpForm {
private String login;
private String password;
private String confirmedPassword;
// Constructor and getters
}
public class EqualFieldsValidator implements ConstraintValidator<EqualFields, Object> {
private String baseField;
private String matchField;
@Override
public void initialize(EqualFields constraint) {
baseField = constraint.baseField();
matchField = constraint.matchField();
}
@Override
public boolean isValid(Object object, ConstraintValidatorContext context) {
try {
Object baseFieldValue = getFieldValue(object, baseField);
Object matchFieldValue = getFieldValue(object, matchField);
return baseFieldValue != null && baseFieldValue.equals(matchFieldValue);
} catch (Exception e) {
// log error
return false;
}
}
private Object getFieldValue(Object object, String fieldName) throws Exception {
Class<?> clazz = object.getClass();
Field passwordField = clazz.getDeclaredField(fieldName);
passwordField.setAccessible(true);
return passwordField.get(object);
}
}
- 커스텀 valid 어노테이션 만들기
@Documented
@Constraint(validatedBy = EmailExistValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EmailExist {
String message() default "Email isn't exist";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Component
@RequiredArgsConstructor
public class EmailExistValidator implements ConstraintValidator<EmailExist, String> {
@Autowired
private AccountRepository accountRepository;
@Override
public void initialize(EmailExist emailExist) {
}
@Override
public boolean isValid(String email, ConstraintValidatorContext cxt) {
boolean isExistEmail = accountRepository.existsByEmail(email);
return isExistEmail;
}
}
어노테이션에 매개변수를 사용하는 경우
@Documented
@Constraint(validatedBy = FileExtensionValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface FileExtension {
//중략...
String[] fileExtension();
boolean nullable();
}
@Component
@RequiredArgsConstructor
public class FileExtensionValidator implements ConstraintValidator<FileExtension, List<MultipartFile>> {
private String[] extensionArray;
private boolean nullable;
@Override
public void initialize(FileExtension fileExtension) {
this.extensionArray = fileExtension.fileExtension();
//중략...
this.nullable = fileExtension.nullable();
}
@Override
public boolean isValid(List<MultipartFile> fileList, ConstraintValidatorContext cxt) {
//중략
}
- @PathVariable , @RequestParam 검증하기
@RestController
@RequestMapping("/api")
@Validated
public class CommentController {
//중략 ...
@PreAuthorize("hasRole('ROLE_MEMBER')")
@PostMapping("/comment/{boardId}")
public ResponseEntity writeComment(@PathVariable @BoardIdExist Long boardId,
@RequestBody @Valid CommentDTO.Write dto,
@ModelAttribute("accountDTO") AccountSecurityDTO accountDTO){
return ResponseEntity.ok(commentService.writeComment(boardId,dto,accountDTO));
}
스프링부트가 아닌 경우 아래 코드를 통해 MethodValidationPostProcessor 빈을 생성해야 합니다.
(부트는 자동으로 생성됨)
@EnableWebMvc
@Configuration
@ComponentScan
public class ClientWebConfigJava implements WebMvcConfigurer {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
// ...
}
컨트롤러의 매핑메소드에서 쓰일것이기 때문에 @Target에 PARAMETER를 추가해야 합니다.
@Documented
@Constraint(validatedBy = BoardIdExistValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface BoardIdExist {
//중략...
}
- @InitBinder와 Validator 사용하기
@InitBinder("signUp")
protected void initBinderSignUp(WebDataBinder binder){
binder.addValidators(accountSignUpValidator);
}
@InitBinder("dto의 클래스명") 형태여야 합니다.
@PostMapping("/account")
public ResponseEntity signUp(@RequestBody @Valid AccountDTO.SignUp dto){
accountService.signUp(dto);
return ResponseEntity.ok(Result.SUCCESS);
}
@Component
public class AccountSignUpValidator implements Validator {
@Override
public boolean supports(Class<?> aClass) {
return AccountDTO.SignUp.class.isAssignableFrom(aClass);
}
@Override
public void validate(Object o, Errors errors) {
AccountDTO.SignUp dto = (AccountDTO.SignUp) o;
if(!isValidPassword(dto)){
throw new NotSamePasswordException("Not same password and password check");
}
}
private boolean isValidPassword(AccountDTO.SignUp dto){
String password = dto.getPassword();
String passwordCheck = dto.getPasswordCheck();
if(!password.equals(passwordCheck)){
return false;
}
return true;
}
}
참고 :
https://www.baeldung.com/javax-validation-method-constraints
https://medium.com/@gaemi/java-%EC%99%80-spring-%EC%9D%98-validation-b5191a113f5c
https://www.baeldung.com/spring-validate-requestparam-pathvariable
http://dolszewski.com/java/multiple-field-validation/
'개발' 카테고리의 다른 글
[spring] database 연결 테스트 코드 (0) | 2019.12.24 |
---|---|
[spring] properties 여러개 사용하기 (0) | 2019.12.24 |
[database] 자주 쓰는 SQL (0) | 2019.12.24 |
[intelij] 인텔리제이 환경설정하기 (0) | 2019.12.24 |
[spring] security와 JWT 사용하기 (1) | 2019.12.23 |
Comments