Request 유효성 검사
서버는 client로 부터 값을 받아서 사용할 때 client로 받은 정보가 서버에서 원하는 형식 혹은 값의 조건에 맞는지 검사를 해야한다. 이를 유효성 검사라고 한다.
이러한 유효성 검사는 class내에 method를 사용하여 검사를 진행했었다.
하지만 유효성 검사는 dto마다 동일하게 혹은 비슷하게 적용 될 수 있고 이에 따라 전체적인 코드 내에서 유효성 검사가 이뤄지는 곳이 여러 Controller에 산재할 수 있다.
따라서 Spring에서는 제약 조건이 부여된 객체에 대해 빈 검증기(Bean Validator)를 이용해서 검증하도록 해주는 어노테이션이 존재한다. 이번에는 이에 대해 정리하려고 한다.
BeanValidation
우리가 사용하는 dto에는 원하는 형식이 존재한다. 특정 변수는 비어있으면 안된다, 혹은 특정 변수는 이러한 값만 가져야한다 등이 그런 것일 수 있다.
이에 Spring에서는 DTO에서 사용하는 변수에 제약 조건을 가해 이를 위반 하였을 때 Exception을 발생시켜준다.
여러 가지 Constraints가 존재하지만 다음과 같이 프로젝트 내에서 사용한 것을 소개하겠다.
- @NotNull
request로 dto를 받을 때 해당 dto에 변수가 null이 되면 안된다는 제약 조건이다.
- @Pattern(regexp = “정규표현식”)
request로 dto를 받았을 때 해당 dto의 변수가 정규표현식에 해당 되어야 한다는 제약조건이다.
예를 들어
public class UserDTO{
@Pattern(regexp = "[MW]")
private String gender;
}
이런식으로 되어있으면 gender라는 변수에는 M또는 W가 존재해야한다.
그럼 이러한 validation은 이제 모든 controller에 적용이 될까?
그러면 너무 strict한 조건일 것이다.
그리고 어떠한 method가 이러한 조건이 필요한지에 대한 정확한 표시도 수반되어야할 것이다.
따라서 리허나 BeanValidation이 적용되는 method는 다음과 같이 인수 부분에 @Valid라는 표현이 들어가야한다.
@Valid
@PostMapping()
public ResponseEntity<?> create(@RequestBody @Valid UserDTO dto){
}
이렇게 메소드의 인수 안에 @Valid라는 표현을 넣으면 앞에서 정의한 BeanValidation의 검사를 받는 dto를 받겠다는 의미이다.
Q) 그럼 만약 이러한 제한 조건을 어긴 request가 들어온다면 어떻게 될까?
A) MethodArgumentNotValidException이 발생한다.
이제 이러한 Exception을 처리해주는 ValidException을 만들어줘야할 것이다.
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> hadlingdtoInValidException(MethodArgumentNotValidException e, HttpServletRequest request){
String errorMessage = e.getBindingResult()
.getAllErrors()
.get(0)
.getDefaultMessage();
ResponseDTO<String> response = ResponseDTO.<String>builder().statusCode(400).error(errorMessage).build();
return ResponseEntity.badRequest().body(response);
}
}
이런식으로 말이다.
여기서 두가지의 어노테이션이 존재하는데
첫번째는 ControllerAdvice이다
해당 어노테이션은 Controller에서 발생하는 Exception을 catch하겠다는 의미이다.
두번째는 ExceptionHandler이다. 해당 어노테이션 구문에 안에 있는 exception.class가 해당 메소드가 catch해서 처리할 exception에 해당한다.
근데 같은 dto라도 적용이 될 곳이 다른 곳이 존재할 수 있다.
예를 들어 만들때는 사용자의 정보가 모두 필요하지만 수정할 때는 사용자의 정보의 일부분만 있어도 되는 것처럼 말이다.
이때 사용하는것이 group과 @Validated이다.
group
group은 validation조건을 적용할 그룹을 선택하게 해주는 값이다.
먼저 그룹에 대한 클래스를 생성한다.
package com.demo.dto;
public class ValidationGroups {
public interface createValidation {};
public interface modifyValidtion {};
}
이렇게 만든 validation 그룹들을 활용하여 다음과 같이 쓸 수 있다
@NotNull(groups = {ValidationGroups.createValidation.class})
private Integer age;
@NotNull(groups = {ValidationGroups.createValidation.class})
@Pattern(groups = {ValidationGroups.createValidation.class, ValidationGroups.modifyValidtion.class}, ...)
private String gender;
사용자를 처음 등록할 때는 age와 gender가 모두 필요할 것이다.
하지만 사용자 정보를 수정할때는 이러한 정보가 항상 필요하지 않을 수 있다.
하지만 두 케이스 모두 gender라는 값에는 성별을 의미하는 문자가 들어와야할 것이다.
따라서 create와 관련된 validation에는 Notnull과 pattern이 모두 적용되고
수정과 관련된 validation에는 pattern만 적용되는 것을 볼 수 있다.
그럼 이걸 controller에서는 어떻게 적용할까?
@Validated(groups = ?)
@PutMapping()
public ResponseEntity<?> modifyInfo(@RequestBody @Validated(ValidationGroups.modifyValidtion.class) UserDTO dto){
}
다음과 같이 Validated라는 어노테이션을 사용하고 해당 어노테이션 안에 내가 적용할 그룹을 명시해주면 된다.
'개발 > Spring' 카테고리의 다른 글
[Spring] Spring Security의 이해 (0) | 2022.01.19 |
---|---|
[Spring] Spring 을 이용한 웹 서비스 구조 (0) | 2022.01.12 |
[Spring]@Transactional과 JUnit Test (2) | 2022.01.11 |
[Spring MVC] DispatcherServlet은 어떻게 request랑 controller를 이어줄까? (0) | 2022.01.06 |
[Spring JPA] ORM과 JPA (1) | 2022.01.05 |