상세 컨텐츠

본문 제목

[스프링2] 12장. MVC 2: 메시지, 커맨드 객체 검증

22-23/22-23 Spring 2

by YUZ 유즈 2022. 12. 29. 10:00

본문

728x90

12장의 키워드

메시지(문자열) 다루기

MessageSource 문자열 관리 인터페이스

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

 

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

<form:error> 에러 메시지 처리 태그

Validator (글로벌 범위, 컨트롤러 범위)

Bean Validation을 이용한 값 검증 처리

 


MessageSource 문자열 관리 인터페이스

스프링은 locale(지역)에 상관없이 일관된 방법으로 문자열(메시지)를 관리할 수 있도록 MessageSource 인터페이스를 제공한다.

특정 지역에서 메시지를 사용할 때 getMessage()를 이용한다.

package org.springframework.context;

import java.util.Locale;

public interface MessageSource {
	String getMessage(String code, Object[] args, String defaultMessage, Locale locale);
    	String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;
    ....
}
  • code: 메시지를 구분하기 위한 파라미터
  • locale: 지역을 구분하기 위한 파라미터 (Locale 타입)
  • args: 인덱스 기반 변수를 전달하기 위한 Object 배열 타입의 파라미터

 

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

별도의 파일로 관리하는 문자열을 JSP 코드에 출력하려면 다음의 3단계를 따른다.

  1. 문자열을 담은 메시지 파일을 작성한다.
  2. 메시지 파일에서 값을 읽어오는 MessageSource 빈을 설정한다.
  3. JSP 코드에서 <spring:message> 태그를 사용해서 메시지를 출력한다.

<spring:message> 태그는 스프링 설정에 등록된 'messageSource' 빈을 이용해 메시지를 구한다.

즉, 내부적으로 MessgaeSource의 getMessage() 메서드를 실행한다.

 

STEP 1. 메시지 파일 작성

메시지 파일은 자바의 프로퍼티 파일 형식으로 작성한다.

예제) src/main/resource 하위에 messages 폴더를 생성하고 label.properties 파일을 생성한다. UTF-8 인코딩을 사용한다.

member.register=회원가입

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

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

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

go.main=메인으로 이동

 

STEP 2. MessageSource 타입 빈 추가

예제) MvcConfig 설정 클래스에 추가한다.

빈의 아이디를 "messageSource"로 지정해야 한다.

import org.springframework.context.MessageSource;
import org.springframework.context.support.ResourceBundleMessageSource;


@Bean
public MessageSource messageSource() {
	ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
    ms.setBasenames("message.label"); // message 패키지의 label 프로퍼티 파일을 읽는다
    ms.setBasenames("message.label", "message.error"); // 불러올 파일 목록(여러 개) 설정 가능
    ms.setDefaultEncoding("UTF-8");
    resturn ms;
}

 

STEP 3. MessageSource를 사용하도록 JSP 코드 수정

라이브러리 설정을 추가한 후 사용한다.

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<!--<spring:message> 커스텀 태그를 사용하기 위해 태그 라이브러리 설정 추가-->
...
<head>
	<title><spring:message code="member.register"/></title> <!--메시지 출력-->
</head>
...
<body>
    <p>
    	<!--메시지 출력-->
    	<spring:message code="register.done"
        	arguments="${registerRequest.name}, ${registerRequest.email}" />
    </p>

 

<spring:message> 태그의 메시지 인자 처리

arguments 속성: code로 지정한 프로퍼티(메시지)의 {인덱스} 위치에 삽입할 값을 설정한다.

// 프로퍼티 파일
register.done=<strong>{0}님 ({1})</strong>, 회원 가입을 완료했습니다.

// JSP 코드 - arguments 속성 이용
<spring:message code="register.done" arguments="${registerRequest.name},${registerRequest.email}" />

// JSP 코드 - <spring:argument> 태그 사용 (위와 동일한 코드)
<spring:message code="register.done">
    <spring:argument value="
${registerRequest.name}" />
    <spring:argument value="
${registerRequest.email}" />
</spring:message>

 

 

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

입력값 검증, 에러 메시지 처리는 애플리케이션을 개발할 때 놓쳐서는 안 된다.

  • 폼에 입력한 값을 검증하지 않으면 잘못된 값이 시스템에 입력되어 애플리케이션이 비정상 동작할 수 있다.
  • 에러 메시지를 제대로 보여주지 않으면 사용자가 곤혹스러워진다.

 

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

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

* Errors와 ValidationUtils 클래스의 주요 메서드는 336p 참고

// Validator 인터페이스 정의

package org.springframework.validation;

public interface Validator {
	boolean supports(Class<?> clazz);  // Validator가 검증할 수 있는 타입인지(타입캐스팅 가능한지) 검사
    void validate(Object target, Errors errors);  // 첫 번째 파라미터로 전달받은 객체를 검증, 오류 결과를 Errors에 담는 기능을 정의
}

아래는 Validator를 구현한 코드이다.

// src/main/java/contorller/RegisterRequestValidator.java

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

...

public class RegisterRequestValidator implements Validator {
     // 이메일 패턴을 검사하는 Pattern 객체 설정
     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;
     @Override
     public RegisterRequestValidator() {
     	pattern = Pattern.compile(emailRegExp);
     }
     
     // clazz 객체가 RegisterRequest 클래스로 타입 변환이 가능한지 확인한다
     @Override
     public boolean supports(Class<?> clazz) {
     	retrn RegisterRequest.class.isAssignableFrom(clazz);
     }
     
     // target 파라미터 : 검사 대상 객체
     // errors 파라미터 : 검사 결과 에러 코드를 설정하기 위한 객체
     @Override
     public void validate(Object target, Errors errors) {
     	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"); // 에러 코드 저장
            }
        }
        // "name" 프로퍼티가 null이거나 공백이면 에러코드로 "required" 추가
        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");
            }
        }
    }
}

위의 Validator를 사용하는 Controller의 코드이다.

// src/main/java/controller/ReeeegisterController.java

import org.springframework.validation.Errors;

@Controller
public class RegisterController {
    private MemberRegisterService memberRegisterService;
    ...
    @PostMapping("/register/step3")
    // Errors 객체는 커맨드 객체의 특정 프로터리 값을 구할 수 있는 getFieldValue() 메서드 제공
    // ValidationUtils.rejectIfEmptyOrWhitespace() 메서드는 커맨드 객체를 전달받지 않아도
    // Errors 객체의 getFieldValue("name") 메서드를 실행하여 프로퍼티 값 접근 가능
    public String handleStep3(RegisterRequest regReq, Errors errors) {
        // 검사
        new RegisterRequestValidator().validate(regReq, errors);
        // 한번이라도 유효하지 않은 값이 존재하면 실행
        if(errors.hasErrors())
            return "register/step2";
        }
    }
}

 

<form:error> 에러 메시지 처리 태그

<label><spring:message code="email" />:<br>
<form:input path="email" />
<form:errors path="email" />
</label>
  • path 속성: 에러 메시지를 출력한 프로퍼티 이름 지정
  • element 속성: 각 에러 메시지를 출력할 때 사용할 HTML 태그 (기본값은 span)
  • delimiter: 각 에러 메시지를 구분할 때 사용할 HTML 태그 (기본값은 <br/>)
사용 예
<form:errors path="userId" element="div" delimeter="" />

 

Validator (글로벌 범위, 컨트롤러 범위)

글로벌 범위 Validator를 위한 2가지 설정

  • 설정 클래스에서 WebMvcConfigurer의 getValidator() 메서드가 Validator 구현 객체를 리턴하도록 구현
  • 글로벌 범위 Validator가 검증할 커맨드 객체에 @Valid 애노테이션 적용
// WebMvcConfigurer

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public Validator getValidator() {
    		return new RegisterRequestValidator();
    }
// @Valid 애노테이션 이용 예

import javax.validation.Valid;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;

@PostMapping("/register/step3")
public String functions(@Valid RegsterRequest regReq, Errors errors) {
	...

컨트롤러 범위 Validator 설정

  • @InitBinder 애노테이션 사용
    (이 애노테이션이 붙은 메서드는 컨트롤러의 요청 처리 메서드가 실행되기 전에 매번 실행된다.
    따라서 아래 코드의 setValidator()를 통해 매번 WebDataBinder가 초기화된다.)
    * WebDataBinder: Validator 목록을 갖고 있는 클래스
import javax.validation.Valid;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;

@PostMapping("/register/step3")
public String functions(@Valid RegsterRequest regReq, Errors errors) {
	...
    
@initBinder
protected void initBinder(WebDataBinder binder) {
	binder.setValidator(new RegisterRequestValidator());
}

 

 

Bean Validation을 이용한 값 검증 처리

Bean Validation 스펙에 포함된 애노테이션을 사용하는 것만으로 커맨드 객체의 값 검증을 처리할 수 있다.

  1. Bean Validation과 관련된 의존을 설정에 추가한다.
  2. 커맨드 객체에 @NotNull, @Digits, @Size 등의 애노테이션을 이용해 검증 규칙을 설정한다.
    Bean Validation 1.1 (350p), Bean Validation 2.0 (351p)

STEP 1. pom.xml

<dependency>
	<groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.1.0.Final</version>
</dependency>
<dependency>
	<groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.4.2.Final</version>
</dependency>

STEP 2. 값 검증 규칙 설정

public class RegisterRequest {
    @NotBlank
    @Email
    private String email;
    @Size(min=6)
    private String password;
    @NotEmpty
    private String confirmPassword;
    @NotEmpty
    private String name;
    ...

STEP 3. 요청 처리 메소드의 파라미터에 @Valid 애노테이션을 붙여 글로벌 범위 Validator로 검증한다.

 


Quiz.

1. 스프링은 locale(지역)에 상관없이 일관된 방법으로 문자열(메시지)를 관리할 수 있도록 (MessageSource 인터페이스)를 제공한다.

2. 메시지 파일은 자바의 (프로퍼티) 형식으로 작성한다.

3. MessageSource 타입의 빈을 추가할 때는 빈의 아이디를 (messageSource)로 해야 한다.

4. 스프링 MVC에서 커맨드 객체의 값이 올바른지 검사하려면 (Validator), (Errors) 인터페이스를 사용한다.

5. 글로벌 범위 Validator 설정: 설정 클래스에서 getValidator() 메서드가 Validator 구현 객체를 리턴하도록 하고, 커맨드 객체에 (@Valid) 애노테이션을 적용한다.

6. 컨트롤러 범위 Validator 설정: (@InitBinder) 애노테이션을 사용한다.

7. (Bean Validation) 스펙에 포함된 (@NotNull), (@Digits), (@Size) 등의 애노테이션을 사용하는 것만으로도 커맨드 객체의 값 검증을 처리할 수 있다.

 

 

코드 문제 1. 조건을 만족하는 <spring:messge>  태그를 작성하라.

다음과 같은 자바 프로퍼티가 있다.
signIn.done=<strong>{0}님 ({1})</strong>으로 로그인했습니다.
이 메시지의 0번 인자에 signInRequest.name, 1번 인자에 signInRequest.email의 값을 표시하려 한다. 

답: <spring:message code="signIn.done" arguments="${signInRequest.name},${signInRequest.email}" />

 

코드 문제 2. 주어지는 글로벌 범위 Validator 대신 컨트롤러 범위 Validator를 사용하도록 코드를 추가하라.

// WebMvcConfigurer

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public Validator getValidator() {
    		return new RegisterRequestValidator();
    }
// @Valid 애노테이션 이용 예

import javax.validation.Valid;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;

@PostMapping("/register/step3")
public String functions(@Valid RegsterRequest regReq, Errors errors) {
	...
import javax.validation.Valid;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;

@PostMapping("/register/step3")
public String functions(@Valid RegsterRequest regReq, Errors errors) {
	...

	/* 이곳에 작성 */
    
}

Corner Spring #2

Editor : 유즈

728x90

관련글 더보기