작심십일일러의 스프링 시작하기(11)-1

서은경·2022년 8월 31일
0

Spring

목록 보기
19/43

<spring:message> 태그로 메시지 출력하기

<label>이메일</label>
<input type="text" name="email"/>

위와 같이 하드코딩 된 뷰 코드는 두 가지 문제가 있다.
1. 동일 문자열을 변경할 때 각 폼을 출력하는 페이지를 찾아 모두 변경해야 한다.
2. 전 세계를 대상으로 서비스를 제공해야 하는 경우 사용자의 언어 설정에 따라 문자열 표시에 어려움이 있다.

이 두 가지 문제를 해결하는 방법이 바로 뷰 코드에서 사용할 문자열을 언어별로 파일에 보관하고, 뷰 코드는 언어에 따라 알맞은 파일에서 문자열을 읽어와 출력하는 것이다! 그러기 위해 다음 작업이 필요하다.

  • 문자열을 담은 메시지 파일 작성
member.register=회원가입

term=약관
term.agree=약관동의
next.btn=다음단계

member.info=회원정보
email=이메일
name=이름
password=비밀번호
password.confirm=비밀번호 확인
register.btn=가입 완료

register.done=<strong>{0}님</strong>, 회원 가입을 완료했습니다.

go.main=메인으로 이동
  • 메시지 파일에서 값을 읽어오는 MessageSource 빈을 설정
    // 빈의 아이디를 messageSource로 지정하지 않으면 정상적으로 동작하지 않음
    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
        ms.setBasenames("message.label");
        ms.setDefaultEncoding("UTF-8");
        return ms;
    }
	이 때 basenames 프로퍼티 값으로 message.label을 주었는데 이는 message 패키지에 속한 label 프로퍼티 파일로부터 메시지를 읽어온다고 설정한 것이다.
  • JSP 코드에서 <spring:message> 태그를 사용하여 메시지 출력
<%@ 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>

<spring:message> 태그의 code 값은 앞서 작성한 프로퍼티 파일의 프로퍼티 이름과 일치한다. MessageSource로부터 코드에 해당하는 메시지를 읽어와 해당 태그 위치에 값이 출력되는 것이다.

메시지 처리를 위한 MessageSourcedhk <spring:message> 태그

스프링은 지역에 상관없이 일관된 방법으로 문자열을 관리할 수 있는 MessageSource 인터페이스를 정의하고 있다.

package org.springframework.context;

import java.util.Locale;

import org.springframework.lang.Nullable;

public interface MessageSource {

	@Nullable
	String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);

	String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;

getMessage() 메서드의 code 파라미터는 메시지를 구분하기 위한 코드이고 locale 파라미터는 지역을 구분하기 위한 Locale이다. 이 기능을 사용하면 국내에서 접근하면 한국어로, 해외에서 접근하면 영어로 메시지를 보여주는 처리가 가능하다.

MessageSource의 구현체로는 자바의 프로퍼티 파일로부터 메시지를 읽어오는 ResourceBundleMessageSource 클래스를 사용한다. 이 클래스는 메시지 코드와 일치하는 이름을 가진 프로퍼티의 값을 메시지로 제공한다.

ResourceBundleMessageSource 클래스는 자바의 리소스번들을 사용하기 때문에 해당 프로퍼티 파일이 클래스 패스에 위치해야 한다. (앞의 설정파일도 클래스 패스에 포함되는 src/main/resources 에 프로퍼티 파일을 위치시킴)

즉 <spring:message> 태그를 실행하면 내부적으로 MessageSource의 getMessage() 메서드를 실행해서 필요한 메시지를 구한다.

String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);

getMessage() 메서드는 인덱스 기반 변수를 전달하기 위해 다음과 같은 Object 배열 타입의 파라미터를 사용한다.

<spring:message> 태그를 사용할 때에는 arguments 속성을 사용해서 인덱스 기반 변수값을 전달하는데

<spring:message code="register.done" arguments="${registerRequest.name}" />

arguments가

register.done=<strong>{0}님</strong>, 회원 가입을 완료했습니다.

이 설정 파일의 {0} 자리에 들어오는 것이다.

두 개 이상의 인자를 사용하려면

  • 콤마로 구분한 문자열
<spring:message code="register.done" arguments="${registerRequest.name},${registerRequest.email}" />
  • 객체 배열
  • <spring:argument> 태그
<spring:message code="register.done">
	<spring:argument value="${registerRequest.name}" />
	<spring:argument value="${registerRequest.name}" />
</spring:message>

를 사용하는 방법이 있다.

커맨드 객체의 값 검증와 에러 메시지 처리

스프링은 폼 값 검증과 에러 메시지 처리를 위해 두가지 방법을 제공한다.

1. 커맨드 객체를 검증하고 결과를 에러코드로 저장

스프링 MVC에서 커맨드 객체의 값이 올바른지 검사하려면 다음의 두 인터페이스를 사용한다.

  • org.springframework.validation.Validator
  • org.springframework.validation.Errors

package org.springframework.validation;
public class UserLoginValidator implements Validator {

public interface Validator {

	boolean supports(Class<?> clazz);

	void validate(Object target, Errors errors);

}

supports() 메서드는 Validator가 검증할 수 있는 타입인지 검사하고 validate() 메서드는 첫 번째 파라미터로 전달받은 객체를 검증하고 오류 결과를 Errors에 담는 기능을 정의한다.

package controller;

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import spring.RegisterRequest;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

// 커맨드 객체의 값 검증을 위한 구현체
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);
    }

    // 파라미터로 전달받은 clazz 객체가 RegisterRequest 클래스로 타입 변환이 가능한지 확인
    @Override
    public boolean supports(Class<?> clazz) {
        return RegisterRequest.class.isAssignableFrom(clazz);
    }

    // target 파라미터는 검사 대상 객체, errors 파라미터는 검사 결과 에러 코드를 설정하기 위한 객체
    @Override
    public void validate(Object target, Errors errors) {
        // 전달받은 target을 실제 타입으로 변환한다
        RegisterRequest regReq = (RegisterRequest) target;

        // email 프로퍼티의 값이 유효한지 검사한다
        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 클래스는 객체의 값 검증 코드를 간결하게 작성할 수 있도록 도와준다
        // errors 객체의 getFieldValue("name") 메서드를 실행해서 커맨드 객체의 name 프로퍼티 값을 구함
        // 따라서 커맨드 객체를 직접 전달하지 않아도 값 검증을 할 수 있음
        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");
            }
        }
    }
}

커맨드 객체를 검증하도록 컨트롤러 수정

	@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 e) {
            // 특정 프로퍼티가 아닌 커맨드 객체 자체에 에러코드 추가
            errors.rejectValue("email", "duplicate");
            return "register/step2";
        }
    }

(자세한 설명은 주석을 확인하면 된다)
validate() 메서드는 검사 대상 객체의 특정 프로퍼티나 상태가 올바른지 검사하고, 올바르지 않다면 Errors의 rejectValue() 메서드를 이용해서 에러 코드는 저장하도록 구현한다.

ValidationUtils 클래스는 객체의 값 검증 코드를 간결하게 작성할 수 있도록 도와준다. if 조건문으로 null을 검색하던 코드를 한줄만으로 완성시킬 수 있다.

‼️ 여기서 주의할 점은 요청 매핑 어노테이션을 붙인 메서드에 Errors 타입의 파라미터는 반드시 커맨드 객체를 위한 파라미터 다음에 위치해야 한다는 점이다.
⭕️ public String handleStep3(RegisterRequest regReq, Errors errors) {
❌ public String handleStep3(Errors errors, RegisterRequest regReq) {

2. JSP에서 에러코드로부터 메시지를 출력

<%@ 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>

출력은 간단하다. <form:errors> 태그를 이용하면 된다. path의 속성은 에러 메시지를 출력할 프로퍼티 이름을 지정하는 것이므로 설정파일에 알맞게 추가해주면 된다.

에러코드애 해당하는 메시지 코드를 찾을 때에는 다음 규칙을 따른다.
1. 에러코드.커맨드객체이름.필드명
2. 에러코드.필드명
3. 에러코드.필드타입
4. 에러코드

프로퍼티 타입이 List나 목록인 경우 다음 순서를 사용해 메시지 코드를 생성한다.
1. 에러코드.커맨드객체이름.필드명[인덱스].중첩필드명
2. 에러코드.커맨드객체이름.필드명.중첩필드명
3. 에러코드.필드명[인덱스].중첩필드명
4. 에러코드.필드명.중첩필드명
5.에러코드.중첩필드명
6.에러코드.필드타입
7.에러코드

예를 들어 코드로 email, 에러코드로 required 로 추가했고 커맨드 객체의 이름이 registerRequest 라면 다음 순서대로 메시지 코드를 검색한다.
1. required.registerRequest.email
2. required.email
3. required.String
4. required

이 중 먼저 검색되는 메시지 코드를 사용한다.

0개의 댓글