MessageSource

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;

    String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
}

국제화(i18n) 및 지역화(l10n)를 위한 메시지 관리 인터페이스(spring core)

동작 과정

  1. 미리 메시지 소스 파일에 메시지 코드와 그에 대응하는 메시지를 키-값으로 정의
    1. 메시지 소스 파일은 Locale별로 구성 가능
    2. 각 메시지 소스 파일에 로케일 정보를 포함시키면 됨(messages_en.properties, messages_ko.properties)
  2. MessageSource를 스프링 컨텍스트에 등록
    1. 스프링 부트의 auto-configuration으로 인해 자동 등록되고 메시지 소스 파일의 이름이 “messages”로 설정됨
    2. 추가적으로 메시지 소스 파일 이름을 정의하려면 spring.messages.basename 속성에 지정
  3. MessageSource.getMessage()를 통해 메시지 소스 파일에 정의된 메시지를 읽어옴
  4. 이 때 code, args, defaultMessage, locale 정보를 파라미터로 받음

MessageSource 구현체

MessageCodesResolver

public interface MessageCodesResolver {
    String[] resolveMessageCodes(String errorCode, String objectName);

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

데이터 바인딩 및 검증 과정에서 발생하는 에러 코드(ObjectError, FieldError)를 메시지 코드로 변환하는 인터페이스

변환된 메시지 코드를 MessageSource를 통해 메시지를 읽어옴

MessageCodesResolver 구현체

DefaultMessageCodesResolver : 특정 필드에 대한 오류 코드를 바탕으로 메시지 코드 생성

DefaultMessageCodesResolver 동작 방식(아래의 순서로 메시지 코드 변환)

BindingResult 또는 Errors 인터페이스와 함께 동작

LocaleResolver

현재 요청의 Locale을 결정하는 객체

MessageSource와 함께 동작하며 결정된 Locale에 맞는 메시지 제공

LocaleResolver구현체

LocalChangeInterceptor

ErrorResponse + MessageSource

MessageSource는 bean validation, spring mvc 표준 예외에 대한 메시지를 처리하는 데 사용되는 경우가 많았음

spring 6.0 부터 추가된 ErrorResponse를 통해 예외 메시지를 처리할 수 있음

// ErrorResponse
default ProblemDetail updateAndGetBody(@Nullable MessageSource messageSource, Locale locale) {
  if (messageSource != null) {
      String type = messageSource.getMessage(getTypeMessageCode(), null, null, locale);
      if (type != null) {
          getBody().setType(URI.create(type));
      }
      Object[] arguments = getDetailMessageArguments(messageSource, locale);
      String detail = messageSource.getMessage(getDetailMessageCode(), arguments, null, locale);
      if (detail != null) {
          getBody().setDetail(detail);
      }
      String title = messageSource.getMessage(getTitleMessageCode(), null, null, locale);
      if (title != null) {
          getBody().setTitle(title);
      }
  }
  return getBody();
}

updateAndGetBody()는 예외 타입, argument, detail message code를 기반으로 MessageSource를 통해 메시지로 변환한 뒤

각각 ProblemDetail의 type, detail, title로 설정하는 메서드임

근데 사실 ProblemDetail은 MessageSource를 사용하지 않고도 직접 예외 메시지를 처리할 수 있기 때문에 편한 방법으로 사용하면 되지 않을까 싶기도 하고