<< 학습 목표 >>
1. Validator 인터페이스를 구현해 커맨드 객체를 검증할 수 있다.
2. ValidationUtils 클래스의 메서드를 사용해 커맨드 객체를 검증할 수 있다.
클라이언트가 보낸 값을 사용하기 전에 반드시 검증해야함
검증은 다음과 같은 것을 해야함
1. 서버에 반드시 필요한 값이라면 클라이언트가 그 값을 보냈는지 확인
2. 클라이언트가 보낸 값이 서버에서 활용할 수 있는 형태인지 확인
검증하지 않고 사용하면 컨트롤러에 오류가 발생할 수도 있고 잘못된 데이터가 저장될 수 있음
studyProject 프로젝트 -> com.study.chapter02 -> ValidationController 클래스를 추가하고 아래 코드를 추가하자
package com.study.chapter02;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class ValidationController {
@PostMapping("/chapter02/validation/type1")
public void type1(Member member) {
System.out.println(member);
}
}
일단 type1 컨트롤러는 별도의 커맨드 객체 검증을 하지 않고 있음
롬복을 사용한 커맨드 객체를 테스트한다는 생각으로 프로젝트를 실행시켜 값을 보내보자
여기서 이제 프로젝트가 2개가 됐음
이때는 내가 실행시키고 싶은 프로젝트를 정확히 확인한 후 실행시켜야함
[ Boot Dashboard ] (1)에서 studyProject 를 실행시키자
앞서 클라이언트가 파라미터를 반드시 전달해야하면 @RequestParam 애너테이션에 required 속성을 true로 설정하면 된다고 했는데 커맨드 객체를 사용했을 때는 @RequestParam 애너테이션을 붙일 수 없음
그럼 DTO 안에 @RequestParam 애너테이션을 붙일 수 있을까? 없음
@RequestParam 애너테이션은 컨트롤러의 매개변수에만 붙일 수 있음
그래서 DTO를 적용했다면 클라이언트가 파라미터를 보내지 않을 수 있음
그래서~! 여기서 커맨드 객체 검증을 배우는 것
서블릿 컨트롤러의 방식대로 직접 커맨드 객체를 검증해도 되지만 굉장히 비효율적임
Spring Framework는 커맨드 객체를 검증하는대 사용할 Validator 인터페이스를 제공하고 있음
Spring Framework 프로젝트는 이 인터페이스를 사용해 커맨드 객체를 검증할 Validator 클래스를 만들어야함
이제 커맨드 객체를 검증할 MemberValidator 를 만들자
studyProject 프로젝트 -> com.study.chapter02 -> MemberValidator 클래스를 추가하고 아래 코드를 추가하자
package com.study.chapter02;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
public class MemberValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Member.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
Member member = (Member) target;
String id = member.getId();
String pw = member.getPw();
if(id == null || id.trim().isEmpty()) {
errors.rejectValue("id", "empty");
} else if(id.length() < 3 || id.length() > 20) {
errors.rejectValue("id", "too short or long");
}
if(pw == null || pw.trim().isEmpty()) {
errors.rejectValue("pw", "empty");
} else if(pw.length() < 6 || pw.length() > 20) {
errors.rejectValue("pw", "too short or long");
}
}
}
<< 코드 설명 (1) >>
위 코드와 설명하기 위해 캡쳐한 코드가 살짝 다른데 위 코드에 더해서 더 다양하게 커맨드 객체를 검증할 수 있음을 표현하기 위함
(1). 커맨드 객체를 검증할 Validator 클래스는 반드시 Validator 인터페이스를 구현해야함
(2). 커맨드 객체가 어떤 커맨드 객체를 검증할 수 있는지 명시하는 부분
return (커맨드객체명).class.isAssignableForm(claszz)
으로 형태가 정해져있음
이 커맨드 객체(MemberValidator)는 Member 커맨드 객체만 검증할 수 있는 Validator임
(3). 커맨드 객체를 검증하는 메서드
이 메서드에는 매개변수가 2개 있음
첫번째 매개변수 - 검증할 커맨드 객체를 전달 받을 매개변수
두번째 매개변수 - 커맨드 객체 검증에 실패했을 때 어떤 사유로 실패했는지 실패 사유를 적어두는 매개변수
<< 코드 설명 (2) >>
(1). 검증하기 위해 첫번째 매개변수로 전달 받은 커맨드 객체를 원래의 형태로 형변환
(2). 커맨드 객체 내 검증할 값들을 꺼냄
(3), (4). 커맨드 객체의 특정 값을 확인하는 부분으로 이와 같이 if문으로 서버에 필요한 값이 있는지 없는지 확인함
(3). 커맨드 객체의 특정 값이 없거나 비어있으면 errors에 이름은 id, 사유는 empty 로 "커맨드 객체 검증 실패" 로 기록
(4). 커맨드 객체의 특정 값이 너무 짧거나 길다면 errors에 이름은 pw, 사유는 too short or long 으로 "커맨드 객체 검증 실패"로 기록
그 이후에 있는 else if ( ... ) { } 은 커맨드 객체 검증에 필요한 코드를 더 쓰면 된다는 의도로 붙여둠
여기서 검증 성공과 검증 실패란 말이 나오는데 검증 성공이라 함은 커맨드 객체가 서버에 필요한 모든 값을 다 갖고 있다는 것
검증 실패라 함은 커맨드 객체 내 특정 값 하나라도 서버에 필요한 값을 갖고 있지 않을 경우임
Member 커맨드 객체를 검증할 MemberValidator 클래스를 만들었으니 이를 사용해 컨트롤러에서 커맨드 객체를 검증해보자
package com.study.chapter02;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class ValidationController {
@PostMapping("/chapter02/validation/type1")
public void type1(Member member, BindingResult errors) {
Validator validator = new MemberValidator();
validator.validate(member, errors);
if(errors.hasErrors()) {
// 커맨드 객체 검증에 실패했을 때
// 필요한 처리
}
if(errors.hasFieldErrors("id")) {
// 커맨드 객체의 특정 값의 검증에 실패했을 때
// 필요한 처리
}
// ...
}
}
<< 코드 설명 >>
(1). 커맨드 객체를 검증하면서 검증에 실패했을 때 사유를 기록할 매개변수를 컨트롤러의 두 번째 매개변수로 선언해야함
그래야 컨트롤러가 동작하면서 사유를 기록할 객체가 만들어짐
(2). 커맨드 객체를 검증하기 위한 Validator 생성
(3). 커맨드 객체(member) 검증
(4), (5). 검증 결과를 확인 하고 검증에 실패했다면 필요한 처리를 함
프로젝트를 실행시키기 전에 커맨드 객체를 검증하는 방법을 다시 한번 자세히 알아보자
(1). 컨트롤러에 errors 매개변수를 선언했는데 이 매개변수는 커맨드 객체 검증 결과를 저장할 객체임
errors 매개변수는 따로 우리가 생성해야하는게 아님
클라이언트가 http://localhost:8080/chapter02/validation/type1 경로로 접근하면 이 컨트롤러(type1) 가 호출됨
이 컨트롤러가 호출되면 Spring Framework는 클라이언트가 보낸 파라미터는 첫 번째 매개변수인 member 에 저장됨
그 다음 Spring Framework가 알아서 errors 매개변수를 만들어줌
(2). errors 객체를 Validator 로 넘겨서 Validator 에서 커맨드 객체 검증에 실패했을 경우 errors에 실패 사유를 기록하는 것
커맨드 객체를 검증하기 위해서 validate 메서드를 호출하는데 validate 메서드로 검증 할 커맨드 객체를 첫번째 매개변수로, 검증 결과를 기록할 객체를 두번째 매개변수로 전달하고 있음
의외로 많은 사람들이 잘 모르는데 메서드를 호출했을 때 메서드의 끝을 만나면 다시 호출했던 제자리로 돌아와(2) 밑으로 내려감
이번에는 validate 메서드 내 코드를 다시 살펴보자
(1). 검증할 커맨드 객체를 원래의 형태로 형변환
(2). 커맨드 객체가 가지고 있는 검증할 값들을 꺼냄
(3). 검증할 값이 서버에 필요한 값을 가지고 있지 않을 경우
(4). errors 객체에 이름은 id로 사유는 empty 로 기록
이 코드가 동작한다면 컨트롤러가 전달해준 errors 에 이와 같이 기록됨
당연하지만 값이 비어있지는 않았지만 길이가 짧거나 길이가 너무 길어 else if 가 동작했다면 다음과 같이 errors에 이름은 id로 사유는 too short or long 으로 기록됨
역시나 당연하지만 값이 비어있지 않았고 길이가 적절했다면 if, else if 모두 건너 뛰고 errors 에는 아무것도 기록되지 않음
값이 비어있어 id, empty 가 기록되어있는 상태라고 하자
아직 validate 메서드의 끝을 만나지 않았으므로 여기서 끝이 아니라 그 다음에 있는 다른 값을 검증하는 if문이 더 동작함
그 다음 if문이 동작해 비밀번호가 비어있지는 않았지만 짧거나 길긴 상황이라 else if 가 동작해 errors 에 다음과 같이 기록된 상황이라고 하자
이제 메서드의 끝을 만났으므로 제 자리(컨트롤러)로 돌아감
그 후 밑으로 내려가 if문들(1, 2)이 동작해 커맨드 객체 검증이 성공했는지 실패했는지 여부를 판단함
(1). hasErrors 메서드는 errors에 사유가 하나라도 기록되어있다면 true를 반환하고 사유가 아무것도 없다면 false를 반환함
(2). hasFieldErrors 메서드는 인자로 넣은 문자열로 기록된 사유가 있다면 true를 반환하고 인자로 넣은 문자열로 기록된 사유가 없다면 false를 반환함
커맨드 객체가 하나라도 비정상적인 값을 갖고 있다면 으로 커맨드 객체를 검증하고 싶다면 hasErrors(1) 메서드를 사용하고 커맨드 객체가 갖고 있는 값 중 일부가 비정상적인 값을 갖고 있다면 으로 커맨드 객체를 검증하고 싶다면 hasFieldErrors(2) 메서드를 사용하면 됨
커맨드 객체 검증 시 특정 값이 비어있는지 여부는 거의 모든 검증에 사용됨
특정 값이 비어있는지 여부를 체크하는 걸 직접 해도 되지만 Spring Framework 에서는 ValidationUtils 클래스로 제공함
MemberValidator 클래스를 아래와 같이 바꾸자
package com.study.chapter02;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
public class MemberValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Member.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
Member member = (Member) target;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "id", "empty");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "pw", "empty");
String id = member.getId();
String pw = member.getPw();
if(id.length() < 3 || id.length() > 20) {
errors.rejectValue("id", "too short or long");
}
if(pw.length() < 6 || pw.length() > 20) {
errors.rejectValue("pw", "too short or long");
}
}
}
<< 코드 설명 >>
코드가 살짝 바뀌었는데 이전 코드와 비교해보자
해당 부분을 ValidationUtils 클래스의 rejectIfEmptyOrWhitespace 메서드로 대체한 것
rejectIfEmptyOrWhitespace 메서드의 첫 번째 매개변수는 검증 결과를 저장할 errors, 두 번째 매개변수는 검증할 멤버 변수 이름, 세 번째 매개변수는 검증에 실패했을 때 실패 사유임
아마 여기서 의문이 드는 분도 있을 것
그렇다면 정확히 본 것
rejectIfEmptyOrWhitespace 메서드로 커맨드 객체를 전달해주지 않았는데 어떻게 커맨드 객체를 검증한다는걸까?
MemberValidator 클래스에 supports 메서드를 오버라이딩 했는데 이 메서드의 역할은 이 Validator 클래스가 어떤 커맨드 객체를 검증할 Validator인지 지정하는 것이라 했음
rejectIfEmptyOrWhitespace 메서드로 커맨드 객체를 전달해주지 않아도 supports 덕분에 어떤 커맨드 객체를 검증해야하는지 알 수 있는 것
그래서 커맨드 객체를 메서드로 넘겨주지 않아도 커맨드 객체를 검증할 수 있음
여기까지 실제 코드가 길진 않았지만 설명이 길었던 커맨드 객체 검증하기를 알아봤음
커맨드 객체 검증이 여기가 끝은 아니고 더 있으니 다음 글을 통해 커맨드 객체 검증을 더 깊게 들어가보자
'Spring + Boot > Boot-Chapter02' 카테고리의 다른 글
Chapter02. Spring Boot - Restful API 이론 (0) | 2023.05.31 |
---|---|
Chapter02. Spring Boot - Validator 유연하게 생성하기 (0) | 2023.04.12 |
Chapter02. Spring Boot - 롬복(Lombok) 사용하기 (0) | 2023.04.11 |
Chapter02. Spring Boot - 클라이언트가 보낸 파일 꺼내기 (0) | 2023.04.11 |
Chapter02. Spring Boot - 클라이언트가 보낸 값들 꺼내기 (0) | 2023.04.09 |