Spring MVC : 메세지 기능 구현

    인프런의 김영한님의 강의를 듣고 복습차원에서 정리한 글입니다. 

     

    메세지 기능


    웹 페이지 전체에 '상품명'이라는 문구가 100개가 출력된다고 가정해보자. 그런데 상품명을 상품이름으로 바꿔달라는 요청이 왔다고 가정해보자. 그렇다면 개발자는 이 모든 문구를 하나하나 찾아가서 수정을 해주어야한다. 화면의 수가 많으면 많을수록 큰 문제가 된다. 

    왜 이렇게 하나씩 찾아가서 수정을 해야할까? 모두 "상품명"이라는 이름으로 하드코딩이 되어있기 때문이다. 그렇기 때문에 하나하나 찾아가서 모든 값들을 수정해야한다. 

    이런 경우를 해결하는데 많은 도움이 되는 것이 바로 메세지 기능이라는 것이다. 메세지 기능을 활용해서 이런 문제점들을 해결해보자! 

     

    아이디어 ! messages.properties 메시지 관리용 파일을 만들고 관리해보자! 

    # messages.properties
    
    item=상품
    item.id=상품 ID
    item.itemName=상품명
    item.price=가격
    item.quantity=수량
    • message.properties에 key = value형식으로 관리용 파일을 만든다.
    • key로 value를 가져와서 HTML에 렌더링 되도록 해본다! 

     

    아이디어 ! 메세지 기능을 활용하면 국제화도 가능하다! 


    #messages_en.properties
    
    item=Item
    item.id=Item ID
    item.itemName=Item Name
    item.price=price
    item.quantity=quantity
    • 기존의 messages 뒤에 _en, _ko 등을 붙여줘서 파일을 만든다.
    • 영어를 사용하는 사람이면 messages_en.properties로 접근해서 값을 읽도록 하면 된다. 
    • 어떤 나라에서 접속한지를 보기 위해서는 HTTP 요청 헤더의 accept-Language를 참고하거나, 사용자가 언어를 직접 선택하도록 하고 쿠키 등을 사용해서 처리하면 된다. 

     

    스프링의 메세지 소스 설정


    스프링의 메세지 소스 직접 등록

    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasenames("messages","errors");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }
    • 스프링은 Message Source를 스프링빈으로 등록해서 사용하면서 메세지 기능을 구현해준다.
    • MessageSource는 Interface이고, 스프링은 구현체로 ResourceBundleMessageResource를 사용한다.
      • 이 때, 사용할 메세지 소스를 "basename"으로 지정해서 등록해준다. 파일은 basename.properties로 등록되게 된다.
    • 보통 UTF-8로 인코딩하기 때문에 setDefaultEncoding으로 인코딩한다.

    이렇게 메세지 기능은 위의 스프링빈을 등록해서 사용할 수 있다. 그렇지만 스프링은 이런 메세지 기능의 구현을 위해서 MessageSource 빈을 자동으로 등록해준다. 따라서 위와 같이 번거롭게 작성할 필요없이, 메세지 소스의 basename만 설정해서 편리하게 사용하면 된다. 

     

    스프링의 Message Source 설정

    #default 설정
    spring.messages.basename=messages
    
    #여러가지 설정 (, , , , , )
    spring.messages.basename=messages, errors
    • 스프링의 Message Soruce를 사용하기 위해서는 어떤 Message Source를 사용할지 지정해주어야 한다.
    • 스프링의 Message Source는 application.properties에서 설정할 수 있다. 
    • default 값은 messages이다. 추가로 더 넣으려면 쉼표로 구분해서 넣으면 된다. 

     

    스프링의 Message Source 국제화 기능 관련


    스프링은 Locale의 정보를 참조해서 가장 앞에 있는 Locale 값부터 불러온다. Locale의 en이 가장 높은 갚이면, messages_en.properties를 자동으로 불러온다. 만약에 en 파일이 없다면, 그 다음으로 높은 Locale의 값을 불러온다. 어떤 파일도 없다면 Default 값인 messages를 불러온다. 

     

    스프링의 Message Source 국제화 기능 사용해보는 테스트 코드


    Message 상태 확인

    #messages.properties
    hello=안녕
    hello.name=안녕 {0}
    
    
    #messages_en.properties
    hello = hello
    hello.name = hello {0}

     

     

    테스트 클래스 만들기

    @SpringBootTest
    public class MessageSourceTest {
    
        @Autowired
        MessageSource ms;
    }
    • Spring Base에서 MessageSource를 사용할 것이기 때문에 @SpringBootTest를 해준다.
    • 등록된 MessageSoruce 빈을 사용해야하기 때문에 @Autowired로 DI해준다.

     

    테스트1. Message 기능 기본

    @Test
    void helloMessage(){
        String result = ms.getMessage("hello", null, null);
        assertThat(result).isEqualTo("안녕");
    }
    • ms.getMessage를 통해서 메세지 기능을 사용할 수 있다.
    • 메세지 코드를 입력하고, 매개변수와 Locale은 넣지 않았다. 

     

    메세지 코드로 검색되지 않을 때 → NoSuchMessageException 발생

    @Test
    void notFoundMessageCode(){
        assertThatThrownBy(() -> ms.getMessage("no_code", null, null)).isInstanceOf(NoSuchMessageException.class);
    }
    • 메세지 코드로 검색되지 않으면 NoSuchMessageException이 발생한다.
    • 예외에 대한 검증이 필요하기 때문에 asserThatThrownBy로 검증했다.

     

    메세지 코드로 검색되지 않을 때, 기본 메세지 설정하기 (DefaultMessage 설정)

    @Test
    void notFoundMessageCodeDefaultMessage() {
        String result = ms.getMessage("no_code", null, "기본 메세지", null);
        assertThat(result).isEqualTo("기본 메세지");
    }
    • 메세지 코드로 검색되지 않을 때, 기본 메세지를 설정하면 Exception이 발생하지 않는다.
    • 메세지 코드로 검색되지 않으면, Default Message가 출력된다. 

     

    매개변수를 활용한 메세지 기능 출력하기

    @Test
    void argumentMessage(){
        String result = ms.getMessage("hello.name", new Object[]{"Spring!", "바보야"}, null);
        assertThat(result).isEqualTo("안녕 Spring!");
    
    }
    • 매개변수를 넘길 때는 Object[] 배열로 넘겨줘야한다. 
    • hello.name = 안녕 {0}으로 되어있는데, {0}은 매개변수로 넘어오는 배열의 인덱스를 가리킨다.
    • 최종 출력물은 "안녕 Spring!"이 되어야 한다. 

     

    Locale로 검색이 되지 않을 때

    @Test
    void defaultLang(){
        String result1 = ms.getMessage("hello", null, null);
        String result2 = ms.getMessage("hello", null, Locale.KOREA);
        assertThat(result1).isEqualTo(result2);
    }
    • Locale에 맞는 메세지가 검색이 되지 않으면, Default Message Soruce가 출력된다.  (messages.properties가 출력)

     

    Locale로 검색이 될 때 

    @Test
    void englishLang(){
        String result = ms.getMessage("hello", null, Locale.ENGLISH);
        assertThat(result).isEqualTo("hello");
    }
    
    • Locale에 맞는 메세지가 처리된다 → 국제화 기능 실행. 

     

     

    메세지 기능을 타임리프에 적용해보기.


    먼저 messages.properties와 messages_en.properties를 다음과 같이 설정한다.

    #messages.properties
    
    label.item=상품2222
    label.item.id=상품 ID2222
    label.item.itemName=상품명2222
    label.item.price=가격2222
    label.item.quantity=수량2222
    
    page.items=상품 목록2222
    page.item=상품 상세2222
    page.addItem=상품 등록2222
    page.updateItem=상품 수정2222
    
    button.save=저장2222
    button.cancel=취소2222
    
    #messages_en.properties
    
    label.item=Item
    label.item.id=Item Id
    label.item.itemName=Item Name
    label.item.price=Price
    label.item.quantity=Quantity
    
    page.items=Item List
    page.item=Item Detail
    page.addItem=Add Item
    page.updateItem=Update Item
    
    button.save=Save
    button.cancel=Cancel

     

    타임리프에 적용하기

    >> 스프링 코드
    ${item.itemName}
    
    >> 자바 코드
    ms.getMessage("item.itemName", null, null);
    ms.getMessage("item.itemName", null, Locale.KOREA);
    • 타임리프에는 #{메세지 코드}을 사용하면 필요한 메세지를 찾아서 치환해준다.
    • 위의 스프링 코드를 치면, 아래의 자바 코드가 실행되는 것으로 이해할 수 있다. 

    위의 방식을 사용해서 모든 View Template에 치환을 해준다. 이후에 실행해보면, 정상으로 실행되는 것을 볼 수 있다.

     

    국제화 기능 사용하기

    좌 : 언어를 한국어로 설정 / 우 : 언어를 영어로 설정

    위처럼 코드를 적용했다면 사실상 국제화가 완료되었다. 국제화가 잘 되었는지 확인하기 위해서는 현재 크롬의 언어 설정을 바꿔서 Accept-Language Header를 바꿔주면 된다. 위의 설정을 참고해서 바꿔주면 된다. 언어 설정을 바꿔주고 실행 결과를 한번 확인해보자! 

    위와 같이 바뀌는 것을 확인했다! 아자! 국제화 기능 수행 완료! 

     

    사용자가 언어 선택해서 국제화 하기! LocaleResolver


    위의 방법은 일반적인 사용자가 하기에는 어렵다. 왜냐하면 기본적으로 누구도 브라우저에서 언어 설정을 바꾸지 않기 때문이다. 따라서 Aceept-Language를 사용한 국제화 기능 말고, 쿠키나 세션 같은 걸로 접근을 할 수는 없을까? 쿠키나 세션 같은 곳에 Locale 값을 보관해두고, 그 Locale 값만 바라보게 한다. 

    이 때, 웹브라우저에 설정된 언어값과는 전혀 상관이 없도록 쿠키나 세션에만 의존하도록 해주려면 어떻게 해야할까? 이럴 때 필요한 것이 LocaleResolver라는 것이다.

    public interface LocaleResolver {
    
       Locale resolveLocale(HttpServletRequest request);
    
       void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);
    
    }
    • 스프링은 Locale 선택 방식을 쉽게 변경할 수 있도록 LocaleResolver라는 인터페이스를 제공해준다.
    • 스프링부트는 기본적으로 Accept-Language Header를 사용하는 AcceptHeaderLocaleResolver를 사용한다. 
    • 만약 Locale 선택 방식을 변경하려면 LocaleResolver의 구현체를 변경해서 쿠키나 세션 기반의 Locale 선택 기능을 사용할 수 있다. 
    • 예를 들어 고객이 직접 Locale을 선택하도록 하고, 그 값을 쿠키에 저장해두고, 그 쿠키가 넘어오게 되면 그 값만 보고  Locale Resolver에서 특정 언어를 보게 해주는 것이다! 

     

     

     

     

    메세지 기능 + 메세지 국제화 정리


    1. Spring의 MessageSoruce는 ResourceBundleMessageSource를 사용한다
    2. MessageSource를 사용하기 위해서는 Basename을 설정해야한다.
    3. BaseName은 application.properties에서 간단히 설정할 수 있다. 
    4. 스프링은 자동으로 MessageSource 빈을 등록해준다. 개발자는 @Autowired로 가져와서 사용하면 된다.
    5. 타임리프에서는 Spring의 메세지를 #{...} 를 사용해서 아주 편리하게 사용할 수 있다. 
    6. . 타임리프에서 #{...}을 사용하면 아래의 코드를 대신 사용해주는 것으로 이해할 수 있다. 
      • 타임리프 : #{item.itemName}
      • Spring : ms.getMessage("item.itemName", null, Locale.Korea)
    7. 크롬에서 언어설정을 바꾸면, Accept-Language Header 값이 바뀌면서 국제화가 적용된 것을 볼 수 있다.

    댓글

    Designed by JB FACTORY