메시지
뷰 jsp의 출력화면의 언어가 한글이 아닌 영어로 보여주고 싶다면, "이메일" -> "email" 이런식으로 한글로 하드코딩했던 모든 부분들을 영어로 다 고쳐줘야할 것이다. 이를 해결하기 위해 나온것이 MessageSource이다.
뷰 코드에서 사용할 문자열을 언어별로 파일에 보관하고 뷰 코드는 언어에 따라 알맞은 파일에서 문자열을 읽어와 출력할 수 있도록 하면 되는데 이를 같이한번 해보자.
메세지 출력 방법 정리
1. src/main/resources/message에 label.properties파일 생성
+ properties파일을 마우스 우측클릭 -> OpenWith->Text Editor로 열자.
+ 그래도 글자가 깨진다면 마우스 우측클릭 -> Properties -> Text Encoding의 값을 'UTF-8'로 설정한다.
2. 스프링 설정클래스에 MessageSource 빈 객체 등록, 메세지 경로 등록
3. jsp에서 메세지를 통해 출력하고 싶은 곳에 <spring:message>태그 사용
label.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
member.register=회원가입
term=약관
term.agree=약관동의
next.btn=다음단계
member.info=회원정보
email=이메일
name=이름
password=비밀번호
password.confirm=비밀번호확인
register.btn=가입완료
register.done=<strong>{0}님({1})</strong>,회원가입을 완료했습니다.
go.main=메인으로 이동
|
cs |
설정클래스 MvcConfig에 빈 추가
1
2
3
4
5
6
7
|
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
ms.setBasenames("message.label");
ms.setDefaultEncoding("UTF-8");
return ms;
}
|
cs |
step1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<!DOCTYPE html>
<html>
<head>
<title><spring:message code="member.register" /></title>
</head>
<body>
<h2>
<spring:message code="term" />
</h2>
<p>약관내용</p>
<form action="step2" method="post">
<label> <input type="checkbox" name="agree" value="true">
<spring:message code="term.agree" />
</label>
<input type="submit" value="<spring:message code="next.btn"/>" />
</form>
</body>
</html>
|
cs |
스프링 설정클래스에서 빈 객로 등록한 MessageSource 인터페이스는
getMessage(String Code, Object[] args, String defaultMessage, Locale locale); 메소드를 가진다.
Code는 메세지를 구분하기 위한 파라미터, locale은 지역을 구분하기 위한 파라미터이다.
다국어 메세지를 지원하려면 프로파티 파일이름에 사용하는 언어에 해당하는 로케일 문자를 추가하면 된다. 예를들면 파일명을 "label_ko.properties", "label_en.properties" 이런식으로 말이다. 이렇게 설정하면 예를 들어 한국에서 생활하는 사람이, 브라우저 언어 설정을 한글로 한 경우 브라우저가 서버에 요청을 전송하면 Accept-Language 헤더에 언어 정보를 담아 전송하는데 이때는 값으로 "ko"를 담아 보낸다.
메시지 Arguments 처리
label.properties의 14행을 보면 {0}, {1} 이런 형식으로 값을 전달 받았다. 이는 인덱스 기반 변수로, {0}는 0번째 인덱스의 값을 받고, {1}는 1번째 인덱스의 값을 받는다는 식으로 이해하면된다. 값을 어떻게 전달했는지는 step3을 보고 확인해보자.
step3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<!DOCTYPE html>
<html>
<head>
<title><spring:message code="member.register" /></title>
</head>
<body>
<p>
<spring:message code="register.done">
<spring:argument value="${registerRequest.name}" />
<spring:argument value="${registerRequest.email}" />
</spring:message>
</p>
<!--
<p>
<spring:message code="register.done"
argument="${registerRequest.name}, ${registerRequest.email}" />
</p>
-->
<p>
<a href="<c:url value='/main'/>"> [<spring:message code="go.main" />]
</a>
</p>
</body>
</html>
|
cs |
11~14행: spring:argument 태그를 이용해 값을 전달했다.
17~20행: spring:message 태그 안의 속성 argument를 이용해 복수의 값을 전달했다.
커맨드 객체 검증
RegisterRequestValidator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
package controller;
import java.sql.Connection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import spring.RegisterRequest;
public class RegisterRequestValidator implements Validator {
// 정규식을 이용해서 이메일 유효성 검사
private static final String emailRegExp = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@"
+ "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
private Pattern pattern;
public RegisterRequestValidator() {
pattern = Pattern.compile(emailRegExp);
System.out.println("RegisterRequestValidator#new(): " + this);
}
@Override
public boolean supports(Class<?> clazz) {
return RegisterRequest.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
System.out.println("RegisterRequestValidator#validate(): " + this);
RegisterRequest regReq = (RegisterRequest) target;
if (regReq.getEmail() == null || regReq.getEmail().trim().isEmpty()) {
errors.rejectValue("email", "required");
} else {
Matcher matcher = pattern.matcher(regReq.getEmail());
if (!matcher.matches()) {
errors.rejectValue("email", "bad");
}
}
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");
}
}
}
}
|
cs |
우리는 회원가입 과정에서 폼(step2)값을 입력하면 RegisterRequest라는 커맨드 객체의 형태로 step3에 전달했다.
우리는 이 커맨드 객체를 검증(올바르게 입력됐는지)하기 위해 Validator를 상속한 ReigsterRequestValidator 클래스를 만들었다.
17~25행: (이메일 검사에서)정규식을 통한 검증을 하기 위한 코드이다.
28행: suppoerts()메서드는 Validator가 검증할 수 있는 타입인지 검사한다. 이 예제에서는 커맨드 객체인
RegisterRequest가 그 대상일 것이다.
33행: validate(Object target, Errors errors) 메서드.
targert파라미터 => 검사 대상
errors파라미터 => 올바르지 않다면 에러가 저장될 errors객체
37행: 에러가 존재하면 .rejectValue("[프로퍼티명]", "[오류코드]") 메소드를 사용하여 errors에 오류를 담는다.
"email" 프로퍼티의 에러코드로 "required"를 추가했다.
에러코드는 spring:message 처럼 JSP코드에서 지정한 에러코드를 이용해 에러 메시지를 출력한다.
44~46행: ValidationUtils 클래스는 객체의 값 검증코드를 간결하게 작성 할 수 있도록 도와준다.
rejectIfEmpty() 메서드 - field에 해당하는 프로퍼티 값이 null 이거나 "" 빈 문자열인 경우 에러코드 추가
rejectIfEmptyOrWhitespace() 메서드 - rejectIfEmpty메서드 기능 + 공백문자(스페이스,탭)인 경우 에러코드
이제 이 검증 클래스를 가지고 어떻게 컨트롤러에서 검증하는지 살펴보자.
RegisterController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package controller;
@Controller
@RequestMapping("/register")
public class RegisterController {
private MemberRegisterService memberRegisterService;
public void setMemeberRegisterService(MemberRegisterService memberRegisterService) {
this.memberRegisterService = memberRegisterService;
}
@PostMapping("/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";
}
}
}
|
cs |
14행: Errors 파라미터는 꼭 커맨드 객체 파라미터 다음에 와야한다. 습관화하자.
15행: 우리가 만든 RegistgerRequestValidator로 검사를 한다.
16행: 검사 결과에 에러가 있다면 has.Errors()가 true가 되어 다시 회원가입 폼을 보여준다.
19행~25행: 이제 Service를 통해 맴버 등록을 하고, 서비스에 의존 주입된 Dao를 통해 DB에 접근한다.
Service 진행 중에 오류가 발생할 수 있다. 아래 RegisterService의 regist()메소드를 보자.
1
2
3
4
5
6
7
8
9
|
public Long regist(RegisterRequest req) {
Member member = memberDao.selectByEmail(req.getEmail());
if (member != null) {
throw new DuplicateMemberException("dup email" + req.getEmail());
}
Member newMember = new Member(req.getEmail(), req.getPassword(), req.getName(), LocalDateTime.now());
memberDao.insert(newMember);
return newMember.getId();
}
|
cs |
폼에 입력한 이메일로 Member를 검색했는데 이미 존재하는 경우 DuplicateMemberException을 일으킨다.
따라서, 서비스 중에 오류가 발생하는 경우 try~catch문을 이용해 Exception을 잡아내서 .rejectValue()메서드로 오류코드를 처리한 다음, 다시 폼을 보여주도록 view를 리턴하면된다.
오류코드를 통한 에러 메시지 출력
label.propertis(오류코드에 따른 message추가)
1
2
3
4
|
required=필수항목입니다.
bad.email=이메일이 올바르지 않습니다.
duplicate.email=중복된 이메일입니다.
nomatch.confirmPassword=비밀번호와 확인이 일치하지 않습니다.
|
cs |
step2.jsp(폼 수정)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
<%@ page contentType="text/html; charset=utf-8" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<!DOCTYPE html>
<html>
<head>
<title><spring:message code="member.register" /></title>
</head>
<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>
<p>
<label><spring:message code="name" />:<br>
<form:input path="name" />
<form:errors path="name"/>
</label>
</p>
<p>
<label><spring:message code="password" />:<br>
<form:password path="password" />
<form:errors path="password"/>
</label>
</p>
<p>
<label><spring:message code="password.confirm" />:<br>
<form:password path="confirmPassword" />
<form:errors path="confirmPassword"/>
</label>
</p>
<input type="submit" value="<spring:message code="register.btn" />">
</form:form>
</body>
</html>
|
cs |
15, 21, 27, 33행: <form:errors> 태그 사용. error가 발생하면 path값을 통해서 해당 오류코드 메세지를 찾아 출력
path속성은 에러 메시지를 출력할 프로퍼티 이름을 지정한다.
에러코드에서 해당하는 메세지 코드를 찾을 때에는 일정한 규칙이 있다. page338쪽 참조.
글로벌 범위 Validator vs Controller 범위 Validator
글로벌 범위: 모든 컨트롤러에 적용할 수 있음
컨트롤러 범위: 단일 컨트롤러 범위에 적용할 수 있음
글로벌 범위 방법:
1. 설정 클래스에서 WebMvcConfigurer의 getValidator() 메서드가 Validator 구현 객체를 리턴하도록 구현
1
2
3
4
|
@Override
public Validator getValidator(){
return new ReigsterRequestValidator();
}
|
cs |
2. 글로벌 범위 Validator가 검증할 커맨드 객체에 @Valid 어노테이션 적용
+ @Valid 어노테이션은 Bean Validation API 포함되어 있으므로 아래 의존 추가
1
2
3
4
5
|
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
|
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@PostMapping("/step3")
public String handleStep3(@Valid RegisterRequest regReq, Errors errors) {
if (errors.hasErrors())
return "register/step2";
try {
memberRegisterService.regist(regReq);
return "register/step3";
} catch (DuplicateMemberException ex) {
errors.rejectValue("email", "duplicate");
return "register/step2";
}
}
|
cs |
끝으로.. Validtor를 통한 검증과정을 적용시켰찌만 Validtor없이 어노테이션만으로 검증처리가 가능하다고 한다.
Bean Validation에서 제공하는 어노테이션이 많으니, 구글링을 참조하면 좋을 것 같다.
출처: 초보 웹 개발자를 위한 스프링5 프로그래밍 입문