본문 바로가기

Backend/SpringBoot

Spring Boot에서 Custom Valid Annotation 만들기

안녕하세요! 이번 포스팅에서는 Spring Boot에서 Custom Valid Annotation를 적용하는 방법에 대해 알아보겠습니다.

전체 코드는 Github에서 확인이 가능합니다. ✍️


스프링에서 제공되는 Valid 관련 어노테이션을은 많지만, 가끔 제공해주지 않는 어노테이션을 Valid 처리하고 싶을 때가 있습니다. 예를 들어, Request 받을 때 전화번호 포맷을 확인하고 싶은데, 이와 관련된 Valid 어노테이션은 없습니다. 그렇다면 아래와 같이 구현해야하는 상황이 생기게 됩니다.

public class Obj {
  @NotBlank
  private String tel;
}
@RestController
public class ValidController {
  @PostMapping("/valid")
  public Obj valid(@RequestBody @Valid Obj object){
	// 전화번호 포맷을 확인하는 로직
    return object;
  }
}

 

하지만 Custom Valid Annotation을 만들면 @Valid 실행 과정에서 함께 유효성 검사가 가능합니다.

public class Obj {
  @Tel
  private String tel;
}

📚 사전준비

1. 사전 준비

위 예제는 예전에 포스팅했던 Validation 예제에 코드를 추가해서 진행합니다.


💻 구현

1. 커스텀 어노테이션 만들기

annotation/Tel.java

@Target(ElementType.FIELD) // 1
@Retention(RetentionPolicy.RUNTIME) // 2
@Constraint(validatedBy = TelValidator.class) // 3
public @interface Tel {
  String message() default "휴대폰 번호"; // 4
  Class[] groups() default {};
  Class[] payload() default {};
}

가장 먼저, 커스텀 어노테이션을 만들어줍니다.

  1. 변수 위에 사용하는 어노테이션이기 때문에 Target은 FIELD로 설정해줍니다.
  2. 어노테이션의 유지범위는 실행하는 동안으로 설정해주기 위해 Retention을 RUNTIME으로 설정해줍니다.
  3. 검증 클래스를 지정해주기 위해 validateBy로 클래스를 지정해줍니다.
  4. 기본적인 메세지를 설정할 수 있습니다.

이외에 groups, payload 설정이 있지만, 따로 설정하지 않겠습니다.

 

2. 검증 클래스 만들기

annotation/validator/TelValidator.java

public class TelValidator implements ConstraintValidator<Tel, String> { // 1

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) { // 2
    if (value == null) {
      return false;
    }

    return value.matches("(01[016789])(\\d{3,4})(\\d{4})");
  }
}

검증 클래스는 검증 로직이 들어가는 클래스입니다.

  1. 검증 클래스를 만들기 위해서는 ConstraintValidator를 구현해주어야합니다. 첫 번째 제네릭 값으로는 검증 어노테이션을, 두 번째 값으로는 필드의 데이터 유형을 넣어줍니다.
  2. isValid를 구현해서 값을 검증할 로직을 넣어줍니다.

 

3. 필드에 어노테이션 추가

dto/Obj.java

@Getter
public class Obj {

	// ...

  @Tel
  private String tel;
}

이제 어노테이션을 추가해보겠습니다!

변수를 추가해주고, 휴대폰 번호의 유효성을 검증하는 역할을 하는 @Tel을 추가합니다. 만약 message가 default로 나오는 값이 아닌 다른 값을 넣고 싶다면, message 속성을 통해 바꿀 수 있습니다.

 

4. 컨트롤러에 @Valid 추가하기

controller/ValidController.java

@RestController
public class ValidController {
  @PostMapping("/valid")
  public Obj valid(@RequestBody @Valid Obj object){
    return object;
  }
}

객체 앞에 @Valid를 추가해줍니다.

 

5. 테스트

이제 구현이 끝났으니 테스트 코드를 작성해보겠습니다!

 

test/controller/ValidControllerTest.java

@RunWith(SpringRunner.class)
@WebMvcTest(ValidController.class)
public class ValidControllerTest {

  @Autowired
  private MockMvc mockMvc;

  @Autowired
  private ObjectMapper objectMapper;

  @Test
  public void 핸드폰번호_validation_성공() throws Exception { // 이름, 나이, 핸드폰번호
    String content = objectMapper.writeValueAsString(new Obj("냠냠이", 10, "01012345678"));

    mockMvc.perform(post("/valid")
        .content(content)
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andDo(print());
  }


  @Test
  public void 핸드폰번호_validation_실패() throws Exception { // 이름, 나이, 핸드폰번호
    String content = objectMapper.writeValueAsString(new Obj("냠냠이", 10, "123456789123"));

    mockMvc.perform(post("/valid")
        .content(content)
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON))
        .andExpect(status().is4xxClientError())
        .andExpect(jsonPath("$.message").value("유효성 검사 실패 : 휴대폰 번호"))
        .andDo(print());
  }
}


✨ 정리

이런식으로 커스텀 어노테이션으로 Controller 또는 Service단에 비즈니스로직을 넣지 않아도 Validation 처리를 할 수 있습니다!

혹시 글을 읽으면서 잘못된 내용이 있으면 댓글로 알려주시면 감사하겠습니다. 읽어주셔서 감사합니다! 😊


👏 참고