Validation์ ํฌ๊ฒ ๋๊ฐ์ง๊ฐ ์๋ค.
- JavaBean ๊ธฐ๋ฐ Validation (๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ)
- Spring Validation ์ธํฐํ์ด์ค ๊ตฌํํ์ฌ Validation
org.springframework.validation.Validatior
์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉํ๋ ๊ฐ์ฒด ๊ฒ์ฆ์ฉ ์ธํฐํ์ด์ค
ํน์ง
- ์ด๋ค ๊ณ์ธต๊ณผ๋ ๊ด๊ณ๊ฐ ์๋ค. -> ๋ชจ๋ ๊ณ์ธต(์น, ์๋น์ค, ๋ฐ์ดํฐ) ์์ ์ฌ์ฉํด๋ ์ข๋ค.
- ๊ตฌํ์ฒด ์ค ํ๋๋ก, JSR-303(Bean Validation 1.0), JSR-349(Bean Validation 1.1) ์ ์ง์ํ๋ค.(LocalValidatorFactoryBean)
- DataBinder์ ๋ค์ด๊ฐ ๋ฐ์ธ๋ฉ ํ ๋ ๊ฐ์ด ์ฌ์ฉ๋๊ธฐ๋ ํ๋ค.
์ธํฐํ์ด์ค : org.springframework.validation.Validator
- boolean support (Class clazz) : ์ด๋ค ํ์ ์ ๊ฐ์ฒด๋ฅผ ๊ฒ์ฆํ ๋ ์ฌ์ฉํ ๊ฒ์ธ์ง ๊ฒฐ์ ํจ.
- void validate(Object obj, Errors e) : ์ค์ ๊ฒ์ฆ ๋ก์ง์ ์ด ์์์ ๊ตฌํ
- ๊ตฌํ ํ ๋ ValidationUitils ์ฌ์ฉํ๋ฉฐ ํธ๋ฆฌํ๋ค.
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/validation/Validator.html
public class UserLoginValidator implements Validator {
private static final int MINIMUM_PASSWORD_LENGTH = 6;
public boolean supports(Class clazz) {
return UserLogin.class.isAssignableFrom(clazz);
}
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName", "field.required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "field.required");
UserLogin login = (UserLogin) target;
if (login.getPassword() != null
&& login.getPassword().trim().length() < MINIMUM_PASSWORD_LENGTH) {
errors.rejectValue("password", "field.min.length",
new Object[]{Integer.valueOf(MINIMUM_PASSWORD_LENGTH)},
"The password must be at least [" + MINIMUM_PASSWORD_LENGTH + "] characters in length.");
}
}
}
์คํ๋ง ๋ถํธ 2.0.5 ์ด์ ๋ฒ์ ์ ์ฌ์ฉํ ๋
- LocalValidatorFactoryBean ๋น์ผ๋ก ์๋ ๋ฑ๋ก
- JSR-380(Bean Validation 2.0.1) ๊ตฌํ์ฒด๋ก hibernate-validator ์ฌ์ฉ.
- https://beanvalidation.org/
Spring boot Validation
์คํ๋ง 2.3.0 ์ด์ ๋ฒ์ ์์ ๋์ด์ Validation์ ์๋์ผ๋ก ๊ฐ์ ธ์ค์ง ์๊ธฐ ๋๋ฌธ์ ์๋ ์์กด์ฑ ์ถ๊ฐ๋ฅผ ํด์ค๋ค.
<!-- ์ด ์ฝ๋๋ฅผ ์ถ๊ฐํด์ค๋ค! -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
DTO์ ๊ฒ์ฆ ์กฐ๊ฑด ์ค์
public class CreateBookRequest {
@NotBlank(message = "์ฑ
์ด๋ฆ์ ํ์๋ก ์
๋ ฅํด์ผํฉ๋๋ค.")
@Size(max=50, message="์ต๋ ๊ธธ์ด๋ 20์
๋๋ค")
private String name;
@Min(0, "๊ฐ๊ฒฉ์ 0์๋ณด๋ค ์ปค์ผํฉ๋๋ค.")
private int price;
// ...
}
์ด๋ฐ์์ผ๋ก ๊ฒ์ฆ์ ํ์ํ ์กฐ๊ฑด๋ค์ ๋ฃ์ด์ค๋ค.
์์ฒญ Body๋ก ๋ค์ด์ค๋ DTO ๋งค๊ฐ๋ณ์์ @Valid ์ด๋ ธํ ์ด์ ๋ง ๋ถ์ฌ์ ํจ์ฌ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
@PostMapping("/books")
public CreateBookResponse createBook(@Valid @RequestBody CreateBookRequest) {
// ...
}
๋ง์ฝ ๊ฒ์ฆ ๋์ค ์คํจํ๋ฉด -> MethodArgumentNotValidException์ด ๋ฐ์!
ExceptionHanlder๋ฅผ ์ด์ฉํด ์๋ฌ๋ฅผ ์๋ ค์ฃผ์
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public 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 errors;
}
์๋ฌ ๋ฐ์ ์:
{
"name": "์ฑ
์ด๋ฆ์ ํ์๋ก ์
๋ ฅํด์ผํฉ๋๋ค.",
"price": "๊ฐ๊ฒฉ์ 0์๋ณด๋ค ์ปค์ผํฉ๋๋ค."
}
Spring validator ์ธํฐํ์ด์ค ๊ตฌํ์ ํตํ validation
- supports: ์ด validator๊ฐ ๋์ํ ์กฐ๊ฑด์ ์ ์, ์ฃผ๋ก ํด๋์ค ํ์ ์ ๋น๊ต
- validate: ์ํ๋ ๊ฒ์ฆ์ ์งํํ๋ค.
public class EventValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Event.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "title", "notEmpty", "Empty title is not allowed.");
// validateํ field(title)๊ฐ empty or whitespace ์ผ๋, errorcode๋ก notEmpty๋ฅผ ์ถ๋ ฅ, defaultMessage๋ errorcode๋ฅผ ์ฐพ์ง ๋ชปํ์๋, ์ถ๋ ฅ!
}
}
package com.example.demospring51;
public class Event {
Integer id;
String title;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
@Component
public class AppRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
Event event = new Event();
EventValidator eventValidator = new EventValidator();
Errors errors = new BeanPropertyBindingResult(event, "event");
//์ด๋ค ๊ฐ์ฒด๋ฅผ ๊ฒ์ฌํ ๊ฒ์ด๊ณ , ์ด๋ค ์ด๋ฆ์ธ์ง ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ
//์ฐ๋ฆฌ๊ฐ ํ
์คํธ์ค์ด๋ผ์ BeanPropertyBindingResult ์ด ํด๋์ค๋ฅผ ์ง์ ๋ง๋ค์ง๋ง, ์ค์ ๋ก๋ spring MVC๊ฐ ์์์ ๋ง๋ค์ด์ ๋งค๊ฐ๋ณ์๋ฅผ ์ ๋ฌํด์ค๋ค.
//ํ์์๋ ์ ํ ์ฌ์ฉํ ์ผ์ด ์๋ค.
event.setTitle("title");
eventValidator.validate(event,errors);
System.out.println(errors.hasErrors());
//error๊ฐ ์๋์ง?
errors.getAllErrors().forEach(e ->{
System.out.println("==== error code ====");
Arrays.stream(e.getCodes()).forEach(System.out::println);
System.out.println(e.getDefaultMessage());
});
}
}
์คํ ๊ฒฐ๊ณผ
true
==== error code ====
notEmpty.event.title
notEmpty.title
notEmpty.java.lang.String
notEmpty
Empty title is not allowed.
title์ด empty์ด๊ธฐ ๋๋ฌธ์, errors.hasErrors(); ๊ฐ true๋ฅผ returnํ๊ณ ,
errors์๋ค๊ฐ ์๋ฌ๋ฅผ ๋ด์์ค๊ฒ์ด๊ณ ,
์๋ฌ ์ฝ๋๊ฐ ์์ฐจ์ ์ผ๋ก ์ถ๋ ฅ์ด ๋๋ค.
error code๋ฅผ ๋ณด๋ฉด, ๋ด๊ฐ ๋ง๋ errorcode์ธ์ notEmpty.event.title / notEmpty.title / notEmpty.java.lang.String์ด ์ธ๊ฐ์ง๊ฐ ์ถ๊ฐ๋ก ์ถ๋ ฅ์ด ๋๋ค.(Validator์์ ์์์ ์ถ๊ฐํด์ค๊ฒ์ด๋ค.)
ValidationUtils๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ์๋ฌ๋ฅผ ๊ฒ์ฆํ๋ ๋ฐฉ๋ฒ (์ ์์ฐ์)
Event event = (Event)target;
if(event.getTitle() == null) {
errors.rejectValue(String field, String errorcode);
}
(ํน์ ํ๋์ ๋ํ ์๋ฌ๊ฐ ์๋๋ผ)์ ๋ฐ์ ์ธ ๊ฐ์ฒด์ ์๋ฌ์ผ๋ -> reject(String errorcode , String defaultMessage)
๊ฐ์ฒด์ ํน์ ํ๋์ ๋ํ ์๋ฌ -> rejectValue(String field, String errorcode , String defaultMessage)
์ต๊ทผ์๋ Validator๋ฅผ ์ฌ์ฉํ์ง์๊ณ , ์คํ๋ง ๋ถํธ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด
๊ธฐ๋ณธ์ ์ผ๋ก ์ฐ๋ฆฌ๊ฐ ๊ตฌํํ Validator์ค์ LocalValidatorFactoryBean์ ๋น์ผ๋ก ์๋๋ฑ๋กํด์ค๋ค. -> spring์ด
์๋ฌด๋ฐ ๋น์ ๋ฑ๋ก์ ์ํ์ง๋ง, ์๋์ ๊ฐ์ด ์ฃผ์ ๋ฐ์์ validate๋ฅผ ์ฌ์ฉ ํ ์์๋ค.
EventValidator eventValidator = new EventValidator();
eventValidator.validate(event,errors);
์์ ์ฝ๋๋ ์ฌ์ฉํ์ง ์๋๋ค.
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
Validator validator;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(validator.getClass());
Event event = new Event();
event.setLimit(-1); //์ผ๋ถ๋ฌ 0์ดํ ๊ฐ์ ๋ฃ๋๋ค.
event.setEmail("a2232");//์ด๋ฉ์ผ์ด ์๋ ๊ฐ์ ๋ฃ๋๋ค.
//EventValidator eventValidator = new EventValidator();
Errors errors = new BeanPropertyBindingResult(event, "event");
//์ด๋ค ๊ฐ์ฒด๋ฅผ ๊ฒ์ฌํ ๊ฒ์ด๊ณ , ์ด๋ค ์ด๋ฆ์ธ์ง ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ
//์ฐ๋ฆฌ๊ฐ ํ
์คํธ์ค์ด๋ผ์ BeanPropertyBindingResult ์ด ํด๋์ค๋ฅผ ์ง์ ๋ง๋ค์ง๋ง, ์ค์ ๋ก๋ spring MVC๊ฐ ์์์ ๋ง๋ค์ด์ ๋งค๊ฐ๋ณ์๋ฅผ ์ ๋ฌํด์ค๋ค.
//ํ์์๋ ์ ํ ์ฌ์ฉํ ์ผ์ด ์๋ค.
//eventValidator.validate(event,errors);
validator.validate(event, errors);
System.out.println(errors.hasErrors());
//error๊ฐ ์๋์ง?
errors.getAllErrors().forEach(e ->{
System.out.println("==== error code ====");
Arrays.stream(e.getCodes()).forEach(System.out::println);
System.out.println(e.getDefaultMessage());
});
}
}
public class Event {
Integer id;
@NotEmpty
String title;
@NotNull @Min(0)
Integer limit;
@Email
String email;
//... getter and setter....
}
๊ฐ๊ฐ ํ๋์ annotation์ ๋ถ์ฌ์คฌ๋ค. ๊ทธ๋ฆฌ๊ณ title์ ๊ฐ์ ์๋ฃ์๊ณ , limit๋ -1, email์๋ ์ด๋ฉ์ผ์ด ์๋๊ฐ์ ๋ฃ์ด์คฌ๋ค.
์คํ ๊ฒฐ๊ณผ
class org.springframework.validation.beanvalidation.LocalValidatorFactoryBean
true
==== error code ====
NotEmpty.event.title
NotEmpty.title
NotEmpty.java.lang.String
NotEmpty
๋น์ด ์์ ์ ์์ต๋๋ค
==== error code ====
Min.event.limit
Min.limit
Min.java.lang.Integer
Min
0 ์ด์์ด์ด์ผ ํฉ๋๋ค
==== error code ====
Email.event.email
Email.email
Email.java.lang.String
Email
์ฌ๋ฐ๋ฅธ ํ์์ ์ด๋ฉ์ผ ์ฃผ์์ฌ์ผ ํฉ๋๋ค
๋งจ ์์ค์ ๋ณด๋ฉด, LocalValidatorFactoryBean์ด ์๋์ผ๋ก ์ฃผ์ ์ด ๋์๋ค.
๊ฒฐ๊ณผ๋, title์ @NotEmpty ๋ฅผ ๋ถ์ฌ์คฌ๊ธฐ ๋๋ฌธ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ์๊ณ ,
limit์ @min(0) , email์ @Email ์ด๋ ธํ ์ด์ ๋๋ฌธ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ์๋ค.
์๊ฐํด๋ณด๊ธฐ
๋ณต์กํ๊ฒ Validation ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํด์ ํ๋ ๊ฒ ๋ณด๋ค ๊ฐ๋จํ๊ฒ 1์ฐจ์ ์ผ๋ก @Valid ์ด๋ ธํ ์ด์ ์ผ๋ก ๊ฒ์ฆํ๊ณ 2์ฐจ์ ์ผ๋ก ๋น์ฆ๋์ค ๋ก์ง์์ ๊ฒ์ฆ์ ํ๋๊ฒ ์ข์ ๊ฒ ๊ฐ๋ค.