순간을 성실히, 화려함보단 꾸준함을

MultipartFile 은 어떻게 validation 체크를 해야할까?(ConstraintValidator.class) 본문

나의 개발 메모장

MultipartFile 은 어떻게 validation 체크를 해야할까?(ConstraintValidator.class)

폭발토끼 2023. 9. 3. 17:25

안녕하세요. 폭발토끼입니다.

 

오늘은 ConstraintValidator 를 사용한 경험에 대해서 공유하려고 글을 작성했습니다. 사실 구글에 너무나도 많은 블로그글들이 많아서 써야할까 고민했지만, 블로그에 기록해두면 앞으로 개발하면서 비슷한 문제를 마주했을때 빠르게 해결할 수 있을 것 같아서 이렇게 기록을 남깁니다.

 

배경

request dto 객체들에 대해서 validation 체크를 해줄때는 @Valid 어노테이션을 사용해서 편하게 체킹할 수 있습니다. 그러나 안타깝게 file class 들에 대해서는 마땅히 체킹할 수 있는 기능이 없습니다. @Valid 의 @NotNull 은 말 그대로 null 인 경우 체킹을 하지만 file 의 경우 아무값이 없을때 null 이 아닌 빈값으로 값이 들어와 필터링을 해줄 수 없기 때문입니다.

그래서 어떻게 하면 controller 에 도달하기 전에 validation 체킹을 할 수 있을지 열심히 서칭해보았습니다.

 

해결

바로 ConstraintValidator 를 사용하여 해결할 수 있었습니다.

5.7 Spring 3 Validation

 

5.7 Spring 3 Validation

Spring provides full support for the JSR-303 Bean Validation API. This includes convenient support for bootstrapping a JSR-303 implementation as a Spring bean. This allows a javax.validation.Validator to be injected wherever validation is needed in your ap

docs.spring.io

스프링 사용자가 custom 하게 데이터들을 필터링 할 수 있게끔 기능을 제공해주는 클래스 입니다.

 

사용방법은 아래와 같습니다.

public class ValidFileValidator implements ConstraintValidator<ValidFile, MultipartFile> {

    @Override
    public boolean isValid(MultipartFile file, ConstraintValidatorContext context) {
        return file != null && !file.isEmpty();
    }
}

'MultipartFile 에 대해서 ValidFile 어노테이션의 제약을 준수했는지 체크한다' 라고 받아들이면 될 것 같아요.

 

저는 MultipartFile 에 대해서 유효성을 체크할 것이기 때문에 file 이 null 이 아니거나 비어있지 않으면 true 를 리턴하게끔 코드를 작성하였습니다.

@Target(value = ElementType.FIELD)
@Retention(value = RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ValidFileValidator.class)
public @interface ValidFile {
    String message() default "Invalid File";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

저는 ValidFile 라는 커스텀 어노테이션 정의해주어 위와 같이 사용했습니다.

@Setter
@Getter
@ToString
public class PostSaveRequestDTO {

    private String content;

    @ValidFile(message = "이미지 파일은 필수입니다.")
    private MultipartFile image;

    /**
     * json 형태의 데이터를 객체형태로 변환하는데 JackSon Library 가 사용된다.
     * 이때, 객체에는 '기본 생성자' 가 반드시 존재해야한다.
     * 이유는 JackSon Library가 기본생성자를 이용하여 객체로 변환시켜주기 때문이다. => 역직렬화
     */
    public PostSaveRequestDTO(){
    }

    @Builder
    public PostSaveRequestDTO(String content, MultipartFile image) {
        this.content = content;
        this.image = image;
    }

    public void validate(){
        if(image.isEmpty()){
            throw new ImageFileArgumentNotValidation("image","이미지 파일은 필수입니다.");
        }
    }
}

결과

해당 api 에 대해 test code를 작성해 볼까요?

    @Test
    @DisplayName("게시글 업로드 : 게시글 업로드시 Image 파일은 필수입니다")
    void uploadPostImageTest() throws Exception {

        //given
        Member member = createMember();
        memberRepository.save(member);

        UserSessionDTO sessionDTO = UserSessionDTO.builder()
                .id(member.getId())
                .email(member.getMemberEmail())
                .nickname(member.getNickname())
                .build();
        String accessToken = jwtManager.makeAccessToken(sessionDTO.getId());

        PostSaveRequestDTO postSaveRequestDTO = createPostRequestDTO();
        postSaveRequestDTO.setImage(null);

        //when
        mockMvc.perform(multipart(COMMON_URL + "/upload")
                        .file("image", null)
                        .header(HttpHeaders.AUTHORIZATION,accessToken)
                        .param("content", postSaveRequestDTO.getContent())
                )
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value("I001"))
                .andExpect(jsonPath("$.message").value("이미지 파일이 존재하지 않습니다"))
                .andExpect(jsonPath("$.validation.image").value("이미지 파일은 필수입니다."))
                .andDo(print());
    }

다행이 통과를 했네요 ㅎㅎㅎ

 

오늘은 ContraintValidator 를 사용하여 커스텀한 validation 체크를 수행할 수 있는 방법에 대해서 알아보았는데요.

이렇게 직접 무엇인가를 만들어봐야지 벽을 느끼고 그걸 하나씩 뿌셔가면서 배울 수 있는 것 같습니다.

 

앞으로도 어떤 문제를 마주하게 될지 궁금하네요 ㅎㅎㅎ

 

감사합니다.