Notice
Recent Posts
Recent Comments
Link
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
Tags
more
Archives
Today
Total
관리 메뉴

끄적끄적

[spring] @Valid 활용하기 본문

개발

[spring] @Valid 활용하기

으아아아앜 2019. 12. 23. 23:51

학습한 내용을 정리한 것입니다.

생략된 내용들과 부정확할 수 있는 내용들에 주의해주세요.

- 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://cnpnote.tistory.com/entry/SPRING-Spring-MVC-PathVariable-%EA%B0%92%EC%9D%84-%EA%B2%80%EC%A6%9D%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9E%85%EB%8B%88%EA%B9%8C

https://www.baeldung.com/spring-validate-requestparam-pathvariable

http://dolszewski.com/java/multiple-field-validation/

 

Comments