에러 메시지

Spring은 메시지, 국제화 기능을 제공하여 다음의 편의성을 제공했다.

  • 메시지 기능을 이용해 자주 등장하는 메시지를 하드 코딩 하지않고 별도의 파일로 분리하여 수정을 용이하게 했다.
  • 국제화 기능을 이용해 각 국가별로 다른 언어로된 메시지를 전달할 수 있도록 하였다.

그런데, Spring은 이 외에도 에러 메시지 기능을 따로 분리하여 에러 메시지만을 처리하기 위한 기능도 제공한다.

사용법

등록

# application.properties
spring.messages.basename = messages,errors.errors

application.properties에 메시지 파일을 등록하는 부분에 , 기호를 이용해 추가로 errors 파일까지 등록할 수 있게 지원한다.

메시지 기능과 동일하게 에러 메시지 기능 또한 각 국가별로 별도의 메시지를 작성할 수 있다.

파일 작성

# src/main/resources/errors/errors.properties
required.item.itemName = 상품 이름은 필수입니다.
range.item.price       = 가격은 {0} ~ {1} 까지 허용합니다.
max.item.quantity      = 수량은 최대 {0} 까지 허용합니다.
totalPriceMin          = 가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}

Validator의 경우,

MessageCodesResolver

package org.springframework.validation;

public interface MessageCodesResolver {
    String[] resolveMessageCodes(String errorCode, String objectName);
    String[] resolveMessageCodes(String errorCode, String objectName, String field, @Nullable Class<?> fieldType);
}

reject과 rejectValue 메서드 내부에서 에러 메시지의 코드를 생성하는 기능을 제공하는 인터페이스이다.

실제 구현체는 기본적으로 DefaultMessageCodesResolver 를 사용하며, 생성 규칙은 다음과 같다.

########################################
# ObjectError(reject)의 경우
1.: code + "." + object name
2.: code

예) 오류 코드: required, object name: item
1.: required.item
2.: required

########################################
# FieldError(rejectValue)의 경우
1.: code + "." + object name + "." + field name
2.: code + "." + field name
3.: code + "." + field type
4.: code

예) 오류 코드: typeMismatch, object name "user", field "age", field type: int
1. "typeMismatch.user.age"
2. "typeMismatch.age"
3. "typeMismatch.int"
4. "typeMismatch"

위의 순서대로 errors.properties에서 해당하는 코드를 찾게 된다.

코드에서..


@PostMapping("/add")
public String addItem(
        @ModelAttribute Item item,
        BindingResult bindingResult
) {
    // totalPriceMin.item 을 찾는다.
    bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);

    // required.item.itemName 을 찾는다.
    bindingResult.rejectValue("itemName", "required");

    // range.item.price 를 찾는다
    bindingResult.rejectValue("price", "range", new Object[]{1000, 1000000}, null);

    // max.item.quantity 를 찾는다.
    bindingResult.rejectValue("quantity", "max", new Object[]{9999}, null);
}

위의 경우, ModelAttribute의 대상이 Item이며, ModelAttribute의 value는 따로 설정하지 않으면 대상 클래스의 이름의 앞글자만 소문자로 바꿔 value에 할당한다. ( 주의할 것이 변수의 이름이 아닌 클래스의 이름을 따라간다는 것이다. 즉, Item itemValue 로 바뀌어도 기본적으로 할당되는 것은 item이 된다. )

그래서, 결과적으로 value의 값이 item이 되어 주석에 작성되어 있는 코드를 생성하여 errors 파일에서 해당하는 Key를 찾게 된다.

순위

# == ObjectError ==
# Level 1
totalPriceMin.item         = 상품의 가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}

# Level 2
totalPriceMin              = 전체 가격은 {0}원 이상이어야 합니다. 현재 값 = {1}

# == FieldError ==
# Level 1
required.item.itemName     = 상품 이름은 필수입니다.
range.item.price           = 가격은 {0} ~ {1} 까지 허용합니다.
max.item.quantity          = 수량은 최대 {0} 까지 허용합니다.

# Level 2 - 생략

# Level 3
required.java.lang.String  = 필수 문자입니다.
required.java.lang.Integer = 필수 숫자입니다.
min.java.lang.String       = {0} 이상의 문자를 입력해주세요.
min.java.lang.Integer      = {0} 이상의 숫자를 입력해주세요.
range.java.lang.String     = {0} ~ {1} 까지의 문자를 입력해주세요.
range.java.lang.Integer    = {0} ~ {1} 까지의 숫자를 입력해주세요.
max.java.lang.String       = {0} 까지의 문자를 허용합니다.
max.java.lang.Integer      = {0} 까지의 숫자를 허용합니다.

# Level 4
required                   = 필수 값 입니다.
min                        = {0} 이상이어야 합니다.
range                      = {0} ~ {1} 범위를 허용합니다.
max                        = {0} 까지 허용합니다.

위의 MessageCodesResolver를 통해 생성된 코드는 메시지를 가져올때 어떤 순서로 가져올지도 정해져 있는데,위의 코드에서 Level이라고 표현된 순서대로 검색하게 되며, 낮은 레벨에서 높은 레벨로 검색하게 된다.

Spring에서 기본 제공되는 오류 메시지 코드

ModelAttribute를 통해 넘어온 데이터를 검증할 때, 프로그래머가 작성한 검증 이전에 각 필드의 바인딩을 시도한다. 이때 타입이 맞지 않아 바인딩에 실패하면 해당 요청이 실패하는게 아닌, reject 메서드를 통해 에러 메시지를 넣게된다. 여기서 기본적으로 제공되는 요류 메시지와 코드가 존재한다.

# price 값이 숫자가 아닌 다른 type이 온 경우,
typeMismatch.item.price
typeMismatch.price
typeMismatch.java.lang.Integer
typeMismatch

이 오류 코드를 errors.properties에 값을 입력하게 되면, 기본적으로 제공되는 메시지 대신에 자신이 원하는 메시지로 대체할 수 있게 된다.

# == Type Error ==
typeMismatch.java.lang.Integer = 숫자를 입력해주세요.
typeMismatch                   = 타입 오류입니다.

Bean Validation의 경우,

Bean Validation에서 제공하는 각종 애노테이션에도 메시지 코드를 사용할 수 있도록 기능을 제공한다.

보통 (애노테이션 이름).(객체 이름).(필드 이름)으로 구성되며 MessageCodesResolver와 마찬가지로 순서에 맞춰 4개를 생성한다.

또한, Bean Validation의 모든 애노테이션은 message 속성을 이용하여 기본 에러 메시지를 정할 수 있다.

NotBlank

NotBlank.item.itemName    = {0}, 값을 입력해야 합니다.
NotBlank.itemName         = {0}, 값을 입력해야 합니다.
NotBlank.java.lang.String = {0}, 값을 입력해야 합니다.
NotBlank                  = {0}, 값을 입력해야 합니다.

메시지 찾는 순서

  1. 생성된 메시지 코드 순서대로 messageSource에서 메시지 찾기
  2. 애노테이션의 message 속성
  3. 라이브러리가 제공하는 기본 값
profile
백엔드 개발자 지망생

0개의 댓글