하드 코딩
하는 방법은 다음과 같은 두 가지 문제가 존재
변경
시다국어
지원파일에 보관
읽어와
출력자체적
으로 제공하며, 다음과 같은 작업을 거친다.
메세지 파일
을 작성MessageSource
빈을 설정<spring:message>
태그를 사용해 메세지를 출력 // MvcConfig.java
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
...
@Bean
// Bean 등록
public MessageSource messageSource() {
ResourceBundleMessageSource ms =
new ResourceBundleMessageSource();
// 읽어올 파일 등록
ms.setBasenames("message.label");
ms.setDefaultEncoding("UTF-8");
return ms;
}
}
MessageSource Bean 객체
를 등록
이때 Bean의 아이디를
"messageSource"
로 지정, 다른 이름은 동작하지 않음
message.label
을 할당
// label.properties
member.register=회원가입
term=약관
term.agree=약관동의
...
<!-- step1.jsp -->
<%@ page contentType="text/html; charset=utf-8" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<!DOCTYPE html>
<html>
<!-- 변경전 코드
<head>
<title>회원가입</title>
</head>
-->
<head>
<title><spring:message code="member.register" /></title>
</head>
...
<spring:message>
태그는 MessageSource
로부터 코드에 해당하는 메세지를 읽어옴지역(로케일)에 상관없이
일괄된 방법으로 문자열(메세지)
를 관리할 수 있는 MessageSource
인터페이스를 정의특정 지역
에 해당하는 메세지가 필요한 코드는 MessageSource의 getMessage()
메서드를 통해 필요한 메세지를 가져와 사용국내
에서 접근하면 한글로, 해외
에서 접근하면 영어로 보여주는 처리 가능
// MessageSource interface
public interface MessageSource{
/*
* code 파라미터: 메세지를 구분하기 위한 코드
* locale 파라미터: 지역을 구분하기 위한 Locale
* 이 기능을 사용하면 지역에 따른 메세지 출력 가능
*/
String getMessage(String code, Object[] args,
Srting defaultMessage, Locale locale);
String getMessage(String code, Object[] args, Locale locale)
throws NoSuchMessageException;
...
다국어 메세지를 지원하려면 각 프로퍼티 파일 이름에 언어에 해당하는
로케일 문자
를 다음과 같이 추가하며
해당하는 지역이 없을 경우,기본값
인 label.properties 파일의 메세지를 사용
label_ko.properties
label_en.properties
브라우저는 서버에 요청을 전송할 때 Accept-Language 헤더에 언어 정보를 담아 전송
- 한글일 경우 헤더값으로 “
ko
“를 전송
Spring MVC
는 웹 브라우저가 전송한Accept-Language 헤더
를 이용해서Locale
을 구하고 메세지를 출력함
<spring:message>
태그를 사용할 때는 arguments
속성을 이용해 인덱스 기반 변수
값을 다음과 같이 전달인덱스
의 정보를 이용해 메세지를 저장
<!-- step3.jsp -->
<spring:message code="register.done" arguments="${registerRequest.name}, ${registerRequest.email}" />
<!-- label.properties -->
register.done=<strong>{0}님 ({1})</strong>, 회원 가입을 완료했습니다.
폼 값 검증
- 웹 페이지에 입력한 값에 대한 검증 필요에러 메세지 처리
- 만약 잘못된 값을 입력하여 다시 페이지가 로드 된다면, 이유를 알려줌검증
하고 결과를 에러 코드
로 저장메세지 출력
인터페이스
를 사용
package org.springframework.validation;
public interface Validator{
boolean supports(Class<?> clazz);
void validate(Object target, Errors errors);
}
supports()
매서드는 Validator가 검증할 수 있는 타입인지 검사validate()
매서드는 첫 번째 파라미터로 전달받은 객체를 검증하고 오류 결과를 Errors에 담는 기능 // RegisterRequesValidator.java
public class RegisterRequestValidator implements Validator {
...
/*
* 파라미터로 전달받은 clazz 객체가 RegisterRequest 클래스로 타입 변환이 가능한지 확인
* 스프링 MVC가 자동으로 검증 기능을 수행하도록 하려면 올바르게 다음 매서드를 구현해야 함
*/
@Override
public boolean supports(Class<?> clazz) {
return RegisterRequest.class.isAssignableFrom(clazz);
}
/*
* 두 개의 파라미터중 target은 검사 대상 객체, errors는 결과 에러 코드를 설정하기 위한 객체
* validate()는 다음과 같이 구현
* 1. 검사 대상 객체의 특정 프로퍼티나 상태가 올바른지 검사
* 2. 올바르지 않다면 Errors의 rejectValue() 매서드를 이용해 에러 코드 저장
*/
@Override
public void validate(Object target, Errors errors) {
System.out.println("RegisterRequestValidator#validate(): " + this);
// 실제 타입으로 변환
RegisterRequest regReq = (RegisterRequest) target;
/*
* "email" 프로퍼티 값이 유효한지 검사
* 유효하지 않다면(NULL이거나 빈 문자열) 에러 코드로 "required"를 추가
*/
if (regReq.getEmail() == null || regReq.getEmail().trim().isEmpty()) {
errors.rejectValue("email", "required");
}
/*
* ValidationUtils 클래스는 객체의 값 검증 코드를 간결하게 작성할 수 있도록 도와줌
* 다음의 코드들은 앞선 email을 검증하는 코드와 동일
*/
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required");
ValidationUtils.rejectIfEmpty(errors, "password", "required");
ValidationUtils.rejectIfEmpty(errors, "confirmPassword", "required");
if (!regReq.getPassword().isEmpty()) {
if (!regReq.isPasswordEqualToConfirmPassword()) {
errors.rejectValue("confirmPassword", "nomatch");
}
}
}
}
필요한 부분
에서 호출 됨
// RegisterController.java
@Controller
public class RegisterController {
...
@PostMapping("/register/step3")
public String handleStep3(RegisterRequest regReq, Errors errors) {
new RegisterRequestValidator().validate(regReq, errors);
if (errors.hasErrors())
return "register/step2";
try {
memberRegisterService.regist(regReq);
return "register/step3";
} catch (DuplicateMemberException ex) {
errors.rejectValue("email", "duplicate");
return "register/step2";
}
}
}
에러 코드
는 알맞은 에러 메세지를 출력하기 위해 지정<form:errors>
태그를 이용해 에러에 해당하는 메세지를 출력
<!-- step2.jsp -->
...
<body>
<h2><spring:message code="member.info" /></h2>
<form:form action="step3" modelAttribute="registerRequest">
<p>
<label><spring:message code="email" />:<br>
<form:input path="email" />
<form:errors path="email"/>
</label>
</p>
...
<form:errors>
태그의 path 속성은 에러 메세지를 출력할 프로퍼티 이름
을 지정MessageSource
를 사용하므로 에러 코드에 해당하는 메세지를 프로퍼티 파일에 추가해야 함
Controller에서 에러가 발생시,
Errors 객체
에 추가하는메시지
를프로퍼티 파일
에 다음과 같이 추가하면 됨
…
required = 필수항목입니다.
bad.email = 이메일이 올바르지 않습니다.
스프링 MVC는 다음 두 가지 Validator를 제공
모든
컨트롤러에 적용할 수 있는 글로벌 Validator
단일
컨트롤러에 적용할 수 있는 Validator
글로벌
범위 Validator를 적용하려면 다음 두 가지 설정 필요
Validator 구현 객체
를 리턴하도록 구현@Valid
어노테이션 적용 // MvcConfig.java
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
/*
* WebMvcConfigurer 인터페이스의 getValidator 매서드를 다음과 같이 오버라이딩하여
* 우리가 원하는 검증을 하도록 설정
*/
@Override
public Validator getValidator(
return new RegisterRequestValidator();
}
}
// RegisterController.java
@PostMapping("/register/step3")
public String handleStep3(@Valid RegisterRequest regReq, Errors errors){
// 기존에 객체를 검증하는 코드를 작성할 필요가 없어짐
// new RegisterRequestValidator().validate(regReq, errors);
...
}
@Valid
어노테이션이 붙은 파라미터는 글로벌 범위 Validator
가 해당 타입을 검증할 수 있는지 확인검증
가능하면 실제 검증을 수행하고 그 결과를 Errors
에 저장실행 전
에 적용
결국 handleStep3() 매서드 안에서 RegisterRequest 객체를 검증하는 코드를 작성할 필요가 없어짐
@InitBinder
어노테이션을 이용해 컨트롤러 범위 Validator를 설정 가능
// RegisterController.java
@PostMapping("/register/step3")
public String handleStep3(@Valid RegisterRequest regReq, Errors errors){
...
}
...
/*
* 어떤 Validator가 객체를 검증할지 다음 @InitBind 어노테이션이 붙은 매서드에서 정의
* 여기서는 RegisterRequest 타입을 지원하는 Validator를 컨트롤러 범위 Validator로 설정
*/
@InitBinder
protected void initBinder(WebDataBinder binder){
binder.setValidator(new RegisterRequestValidator());
}
없음
@InitBind
어노테이션을 적용한 매서드는 WebDataBinder
타입 파라미터를 가짐setValidator()
매서드를 이용해 컨트롤러 범위에 적용할 Validator 설정 가능
글로벌 범위와 컨트롤러 범위 validator()가
모두
존재할 경우,
글로벌
->컨트롤러
범위 순으로 validator()가 적용됨
@Valid
어노테이션은 Bean Validation 스펙에 정의되어 있는데, @NotNull
, @Digits
, @Size
등의 어노테이션과 함께 정의되며,
Bean Validaion이 제공하는 어노테이션을 이용해 커맨드 객체의 값을 검증
하는 방법은 다음과 같다.
Bean Validation
과 관련된 의존
을 설정에 추가(Maven의 경우 pom.xml에 추가)커맨드 객체에 @NotNull, @Digits 등의 어노테이션을 이용해 검증 규칙
을 설정
// RegisterRequest.java
public class RegisterRequest {
@NotNull
@Email
private String email;
@Size(min=6)
private String password;
@NotEmpty
private String confirmPassword;
@NotEmpty
private String name;
...
검증
할 수 있는OptionalValidatorFactoryBean
클래스를 Bean으로 등록@EnableWebMvc
어노테이션을 사용해 OptionalValidatorFactoryBean
을 글로벌 범위
Validator로 등록@Valid
어노테이션을 붙여 글로벌 범위 Validator
로 검증 가능기본 에러 메세지
를 출력
@NotNull: 반드시 값이 존재하고 공백 문자를 제외한 길이가 0보다 커야 합니다.
@NotEmpty: 반드시 값이 존재하고 길이 혹은 크기가 0보다 커야 합니다.
…
@PostMapping("/register/step3")
public String handleStep3(@Valid RegisterRequest regReq, Errors errors) {
new RegisterRequestValidator().validate(regReq, errors);
if (errors.hasErrors())
return "register/step2";
try {
memberRegisterService.regist(regReq);
return "register/step3";
} catch (DuplicateMemberException ex) {
errors.rejectValue("email", "duplicate");
return "register/step2";
}
}
만약 글로벌 validator가 따로 설정되어 있다면,
Spring은 OptionalValidatorFactoryBean를 글로벌 범위 Validator로사용하지 않음
따라서 따로 설정한 글로벌 범위 validator는삭제
해 주어야 함
기본 에러 메세지 대신, 원하는 에러 메세지를 사용하기 위해 다음과 같이 properties 파일
에 작성
NotBlank=필수 항목입니다. 공백 문자는 허용하지 않습니다.
NotEmpty=필수 항목입니다.
Size.password=암호 길이는 6자 이상이어야 합니다.
Email=올바른 이메일 주소를 입력해야 합니다.
@NotBlank: 필수 항목입니다. 공백 문자는 허용하지 않습니다.
@NotEmpty: 필수 항목입니다.
…