스프링 Custom Bean Validation 만들어서 사용해보기(어노테이션 @Email)
- 웹 개발/Spring Framework
- 2020. 7. 18. 12:41
스프링 Bean Validation 이란??
스프링에서는 JavaBean(getter와 setter를 가지고 있는 자바객체, 흔히 VO, DTO라고 부르는 것들)의 유효성 작업을 진행하기 위해 javax.validation 패키지 내에 있는 여러가지 유효성 관련 클래스들을 활용할 수 있습니다.
* 참고로 스프링 부트 2.3 버전 이후부터는 Spring Web 유효성에서 Validation 의존성이 따로 분리되어 별도로 Validation 의존성을 추가해야 javax.validation 패키지를 활용하여 여러가지 유효성 검사를 어노테이션을 통해 활용할 수 있습니다. |
대표적으로 오늘 살펴볼 ConstraintValidator 인터페이스를 구현하여 아래와 같이 Email의 유효성 검사를 할 수 있는 EmailValidator를 생성하여 어노테이션만으로 해당 필드의 유효성 검사를 진행할 수 있습니다.
예제를 함께하시고 정상적으로 Bean Validator를 생성했다면 아래와 같이 IDE의 Bean Validation 탭을 펼쳐서 열어보면 작성한 EmailValidator를 확인해보시면 될 것 같습니다.
모든 소스는 이곳에 있습니다.
예제 프로젝트 구조
- @Email 어노테이션을 통해 javax.validation의 어노테이션과 마찬가지로 Validation 작업을 진행해보려고 합니다.
- UserController을 만들어서 간단하게 api를 만들어볼 생각입니다.
- UserParam은 Email을 request Body로 담을 건데 그 그릇입니다.
- TDD는 아니었지만 Test 코드를 작성하여 api를 호출할 UserControllerTests 클래스를 생성하였습니다.
예제 코드
UserController.java
package com.example.demo1;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/user", produces = MediaType.APPLICATION_JSON_VALUE)
public class UserController {
@PostMapping
public ResponseEntity addUser(@RequestBody @Validated UserParam userParam)
throws Exception {
return new ResponseEntity(userParam, HttpStatus.OK);
}
}
- 간단히 @PostMapping으로 Request Body를 UserParam으로 받고 유효성만 검사한 후, 그대로 리턴해줍니다.
UserParam.java
package com.example.demo1;
import lombok.*;
import javax.validation.constraints.NotEmpty;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class UserParam {
@NotEmpty(message = "이메일을 입력해시기 바랍니다.")
@Email
private String email;
}
- email이 null or 빈문자열이 아니면 일단 @Email 어노테이션의 로직을 검사하게 될 것입니다.
UserControllerTests.java
package com.example.demo1;
import com.fasterxml.jackson.databind.ObjectMapper;
import jdk.nashorn.internal.ir.annotations.Ignore;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTests {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
@Ignore
public void addUser() throws Exception {
UserParam param = new UserParam();
param.setEmail("doqndnffogmail.com");
mockMvc.perform(post("/user")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(param)))
.andDo(print())
.andExpect(status().isOk());
}
}
- param의 email을 @없이 이상하게 추가하였습니다.
@Email 어노테이션
package com.example.demo1;
import org.springframework.util.StringUtils;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.*;
import java.util.regex.Pattern;
import static java.lang.annotation.ElementType.FIELD;
@Constraint(validatedBy = Email.EmailValidator.class)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(FIELD)
public @interface Email {
String message() default "이메일이 양식에 맞지 않습니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default{};
class EmailValidator implements ConstraintValidator<Email, String> {
private final String REGEX_EMAIL = "[a-z0-9][a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])";
public Pattern email = Pattern.compile(REGEX_EMAIL);
@Override
public boolean isValid(String s,
ConstraintValidatorContext constraintValidatorContext) {
System.out.println("email validation");
if(StringUtils.isEmpty(s)) {
return true;
} else {
return email.matcher(s).matches();
}
}
}
}
- @Retention과 @Target에 대해서 알고싶으시다면 다음을 참조해주세요.
https://sas-study.tistory.com/329 / https://sas-study.tistory.com/276
- @Constraint 어노테이션의 validatedBy 속성에 Email의 유효성을 검증할 Validator의 Class 타입을 지정해줍니다.
- Validator는 EmailValidator로 위에서 언급했던 ConstraintValidator를 구현합니다. 간단히 isValid 메소드만 구현하였습니다.
- isValid의 String에는 Request Body를 통해 넘어온 email 값이 넘어오게 됩니다. 이를 정규식으로 matches만 해주면 깔끔하게 유효성 검사를 마칠 수 있습니다.
만약 유효성검사가 제대로 진행되지 않으신다면 Controller를 확인해보셔서 @Validated 어노테이션을 UserParam앞에 잘 달아놨는지 확인해보시기 바랍니다. 또한, 아래처럼 Bean Constraints에 @Email 어노테이션이 잘 등록됐는지 확인해보시면 될 것 같습니다.
결과 확인
response에 아무것도 나오지 않아서 조금 애매합니다. UserController를 수정하여 메시지만 출력해보도록 하겠습니다.
수정된 UserController의 addUser 메소드
@PostMapping
public ResponseEntity addUser(@RequestBody @Validated UserParam userParam,
Errors errors) throws Exception {
if (errors.hasErrors()) {
return ResponseEntity.badRequest().body(errors.getFieldError().getDefaultMessage());
}
System.out.println(userParam);
return new ResponseEntity(userParam, HttpStatus.OK);
}
Errors 객체는 에러가 발생하면 에러를 담습니다. 이전에는 바로 튕겨내어 Controller에 진입조차 못했다면 Errors 객체를 통해 error에 대한 데이터를 가지고 Controller에서 판별할 수 있습니다.
수정된 결과
'웹 개발 > Spring Framework' 카테고리의 다른 글
Restful API 구현을 위한 Spring Security 설정해보기 #1(SecurityConfig Java 설정) (4) | 2020.08.15 |
---|---|
Restful API 구현을 위한 Spring Security 설정해보기 #0 (스펙정의, 사전 작업 등) (2) | 2020.08.14 |
[Spring] Dependency Injection을 하는 다양한 방법!(@Autowired, Setter 주입, 생성자 주입) (4) | 2020.06.28 |
[Spring Framework] Java 리플렉션을 이용한 Dependency Injection(의존성 주입) 예제 로직 구현 (0) | 2020.01.06 |
[Spring] 스프링 프레임워크 Lombok 라이브러리 (0) | 2019.10.28 |