
개발자는 값에 대한 검증을 여러 방면에서 수행해야 합니다.
스프링은 웹 레이어에 종속적이지 않은 방법으로 Validation을 하려고 의도하고 있으며 주로 아래 두 가지 방법을 활용하여 Validationd을 진행합니다.
@NotBlank
@Size
@Min
@Email
등의 어노테이션을 이용한 방법입니다. 요청 dto에 어노테이션으로 명시한 후 @Valid 어노테이션을 @RequestBody에 달게 되면, Java Bean Validation을 수행한 후 문제가 없을 때만 메서드 내부로 진입합니다. 검증 중 실패가 발생하면 MethodArgumentNotValidException이 발생합니다.
예시 코드
import javax.validation.constraints.Email;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
public class UserRequestDTO {
@NotBlank(message = "Name must not be blank")
@Size(min = 2, max = 30, message = "Name must be between 2 and 30 characters")
private String name;
@NotBlank(message = "Email must not be blank")
@Email(message = "Email should be valid")
private String email;
@Min(value = 18, message = "Age must be at least 18")
private int age;
...
}
--------------------------------------------------------
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
@RestController
public class UserController {
@PostMapping("/users")
public ResponseEntity<String> createUser(@Valid @RequestBody UserRequestDTO userRequestDTO) {
// 정상적인 검증이 통과되면 이 메서드가 실행됩니다.
return ResponseEntity.ok("User created successfully");
}
// Validation 실패 시 MethodArgumentNotValidException이 발생하며, 아래의 핸들러가 호출됩니다.
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}
Java Bean Validation 보다 복잡한 검증이 필요할 때 사용할 수 있는 방법입니다.
예시 코드
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
@Component
public class UserRequestValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return UserRequestDTO.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
UserRequestDTO userRequest = (UserRequestDTO) target;
// 공백 체크
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "name.empty", "Name must not be empty");
// 이메일 형식 체크
if (userRequest.getEmail() != null && !userRequest.getEmail().contains("@")) {
errors.rejectValue("email", "email.invalid", "Email should be valid");
}
// 나이 체크 (예: 최소 나이 18세)
if (userRequest.getAge() < 18) {
errors.rejectValue("age", "age.invalid", "Age must be at least 18");
}
}
}
-----------------------------------------
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
private final UserRequestValidator userRequestValidator;
@Autowired
public UserController(UserRequestValidator userRequestValidator) {
this.userRequestValidator = userRequestValidator;
}
@PostMapping("/users")
public ResponseEntity<String> createUser(@RequestBody UserRequestDTO userRequestDTO, BindingResult result) {
// 커스텀 검증 로직 수행
userRequestValidator.validate(userRequestDTO, result);
if (result.hasErrors()) {
// 검증 실패 시, 에러 메시지를 반환
return ResponseEntity.badRequest().body(result.getAllErrors().toString());
}
// 정상 처리
return ResponseEntity.ok("User created successfully");
}
}
Spring Validator 인터페이스 사용 시 주의 사항은 아래와 같습니다.
데이터 바인딩을 위해 데이터 컨버터가 필요할 수 있습니다. S 타입을 T 타입으로 변환해주는 컨버터에 대해 알아보겠습니다.
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
@Component
public class UserRequestDTOToUserConverter implements Converter<UserRequestDTO, User> {
@Override
public User convert(UserRequestDTO source) {
User user = new User();
user.setName(source.getName());
user.setEmail(source.getEmail());
// 나이는 String에서 int로 변환합니다.
try {
user.setAge(Integer.parseInt(source.getAge()));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid age format: " + source.getAge());
}
return user;
}
}
-------------------------
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
private final ConversionService conversionService;
@Autowired
public UserController(ConversionService conversionService) {
this.conversionService = conversionService;
}
@PostMapping("/users")
public ResponseEntity<String> createUser(@RequestBody UserRequestDTO userRequestDTO) {
// ConversionService를 이용하여 UserRequestDTO를 User로 변환합니다.
User user = conversionService.convert(userRequestDTO, User.class);
if (user != null) {
return ResponseEntity.ok("User created successfully: " + user.getName());
} else {
return ResponseEntity.badRequest().body("Conversion failed");
}
}
}