
얘는 왜 검정색일까
validation은 중요하다 그리고 귀찮다
Validation은 매우 중요하지만 귀찮은 일이다. 이렇게 귀찮은 일을 쉽게 구현해 둔 사람이 없을리 없다. Spring 뿐 아니라 Java에서도 validation을 위한 방법을 제공하고 있으며, Spring에서는 Java의 validation 외에도 validation을 위한 것들을 제공한다.
@Valid annotation은 spring의 validation이 아닌 java의 Bean Validation을 위한 annotation이다. jakarta.validation class에 있으며, jakarta.validation.constraints에 있는 annotation들과 함께 사용하여 유효성 검사를 수행할 수 있다.
아래와 같이 request를 위한 DTO를 생성하고
@NoArgsConstructor
@Getter
@Setter
public static class SignupReqDto {
private String id;
@NotEmpty(message = "Password cannot be empty string")
@Pattern(regexp = "^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[!@#$%^&*()._-])[a-zA-Z0-9!@#$%^&*()._-]{8,20}$", message = "Invalid password")
private String password;
@NotEmpty(message = "Name cannot be empty string")
private String name;
@NotEmpty(message = "Email cannot not be empty string")
@Email(message = "Invalid email format")
private String email;
@Pattern(regexp = "^((0)[1-9][0-9]{1,2})-?[0-9]{3,4}-?[0-9]{4}$", message = "Invalid phone number format")
private String contact;
}
아래와 같이 controller를 구현하여 유효성 검사를 수행하도록 한다.
@PostMapping("/signup")
public ResponseEntity<Map<String, Object>> signup(@Valid @RequestBody User.SignupReqDto signupReqDto) { ... }
DTO의 field에 적절한 constraint를 부여하고, controller에서 @Valid annotation을 붙여주면 된다.
위와 같이 controller를 구현하고 잘못된 request를 보내면 자동으로 400 Bad Request 응답이 오는것을 확인할 수 있다. 또한 controller 내부에 로그를 남기도록 코드를 작성 하더라도, 해당 로그가 찍히지 않는것을 확인할 수 있을 것이다.
이는 Spring에서 request body의 유효성 검사를 수행하는 방식을 통해 알아볼 수 있다.
Spring은 request를 수신하고 Dispatcher Servlet에서 DTO 객체를 mapping 하는 과정을 거치는데, 해당 과정에서 유효성 검사를 수행하고 검사를 통과하지 못하는 경우 MethodArgumentNotValidException을 발생시키고 controller의 method를 호출하지 않는다. 즉, Controller 내부에서 Exception을 받아 처리하고 response를 전송할 수 없다.
Client의 잘못된 요청이니 그냥 400 응답을 보내도 되겠지만 싫어할거다. 어느 요청이 잘못된 것인지 알려줄 필요가 있을수도 있고, 개인적으로 원하는 로그를 남기고 싶을수도 있으니 유효성 검사에서 발생한 Exception을 handling하는 방법을 알아보자.
위 작성한 controller의 method에 Errors parameter를 추가해주면 된다.
@PostMapping("/signup")
public ResponseEntity<Map<String, Object>> signup(@RequestBody User.SignupReqDto signupReqDto, Errors errors) {
if(errors.hasErrors()) {
result.put("errors", errors.getAllErrors());
return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
}
위와 같이 작성하면, error가 발생하더라도, errors 객체를 controller의 method에 전달하여 호출하도록 할 수 있다.
물론 다른 방법으로 exception을 handling 할 수 있지만, 위 방법을 통해 controller의 method가 호출되도록 할 수 있다.
위에서 유효성 검사는 Dispatcher Servlet에서 DTO 객체를 mapping할 때 수행 된다고 했다. 그렇다면 @Valid annotation은 Controller가 아닌 곳에서 쓸 수 없나? 안되는게 어딨냐
@Validated위 @Valid annotation은 java의 validation이며, @Validated annotation은 Spring의 validation을 위한 것이다. AOP 기반으로 동작하며, 필요한 Spring bean에 작성하여 해당 객체의 method 호출 시 validation을 수행하도록 할 수 있다.
아래는 Controller가 아닌 Service에서 validation을 수행하는 코드이다
Service
@Validated
public class UserService {
private final UserRepository userRepository;
public User signup(@Valid User.SignupReqDto signupReqDto) throws Exception {
...
}
Service에 @Validated annotation을 추가해 service 객체가 method 호출 시 유효성 검사를 할 수 있도록 advice를 추가하도록 하고, DTO에 @Valid annotation을 통해 유효성 검사가 수행되어야 함을 알린다.
@Validated annotation은 AOP 기반으로 동작하며, validation 실패시 ConstraintViolationException을 발생시킨다.