spring-boot-2.0.3源碼篇 - 國際化

前言

       針對spring boot,網上已有不少優質的系列教程,我就再也不班門弄斧了(其實是擔憂沒別人寫的好,哈哈哈!)。可是仍是想蹭蹭spring boot的熱度,即便不考慮微服務,spring boot仍是有不少可取優勢的,好比自動化配置、系列Starters簡化maven的依賴管理等。本系列主要是將工做中涉及到的一些功能利用spring boot整合到一塊兒(工做中還沒用到spring-boot)。java

       maven-ssm-web中的內容會陸續集成進來,最近幾篇博客會先介紹一些maven-ssm-web中沒有的新內容(由於比較熟嘛!);maven-ssm-web最近會停更,若是有朋友仍須要,仍是會繼續更新的;spring boot的集成工程是:spring-boot-integrate,系列工程則是: spring-boot-2.0.3git

       該系列工程都是基於spring-boot-2.0.3;本文是第一篇,先來點簡單的,講講spring boot中的國際化,工程地址:spring-boot-i18ngithub

基本版

  pom.xml:web

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com .lee</groupId>
    <artifactId>spring-boot-i18n</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

  application.ymlspring

server:
  port: 8880
spring:
  #國際化配置
  messages:
    encoding: utf-8
    basename: i18n/messages
  #thymeleaf配置
  thymeleaf:
    cache: false

  I18nConfig.javaapache

package com.lee.i18n.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;

@Configuration
public class I18nConfig {

    // 配置cookie語言解析器
    @Bean
    public LocaleResolver localeResolver() {
        CookieLocaleResolver resolver = new CookieLocaleResolver();
        resolver.setCookieMaxAge(3600);      // cookie有效時長,單位秒
        resolver.setCookieName("Language");  //設置存儲的Cookie的name爲Language
        return resolver;
    }

    // 配置一個攔截器,攔截國際化語言的變化
    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            //攔截器
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(new LocaleChangeInterceptor()).addPathPatterns("/**");
            }
        };
    }
}
View Code

  LoginController.javasegmentfault

package com.lee.i18n.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class LoginController {

    @RequestMapping("/login")
    public String login() {
        return "login";
    }

    @RequestMapping("/index")
    public String index() {
        return "index";
    }
}
View Code

  messages.propertiescookie

index.welcome=歡迎

login.username=登陸名
login.password=密碼
login.login=登陸

  messages_en_US.propertiesmvc

index.welcome=welcome

login.username=username
login.password=password
login.login=login

  messages_zh_CN.propertiesapp

index.welcome=歡迎

login.username=登陸名
login.password=密碼
login.login=登陸

  當如上文件配置好以後(其餘的能夠去spring-boot-i18n拉取),都配置好後,工程跑起來,咱們來看看結果,是否達到國際化效果呢? 答案是確定的嘛!

高級版

  基本版有一個缺點,就是國際化資源都寫在了一個文件中:messages*.properties,內容都寫在一個文件中有一個致命的缺點:文件越大,越難以維護;

  那麼高級版高級在哪了? 你想的沒錯,就是將資源按某種性質或者功能劃分紅資源文件夾,再在資源文件夾下放具體的資源文件,以下圖

  改動的內容已標明,具體改動的內容能夠去spring-boot-i18n拉取;工程跑起來,咱們看看結果

源碼探究

  從兩個容器的初始化來看整個過程,是哪兩個容器了,一個是spring根容器、一個是spring mvc容器,spring根容器也就是根上下文:WebApplicationContext,spring mvc容器便是:DispatcherServlet;

  spring根容器初始化

    咱們從main函數入手,以下圖

    initMessageSource():初始化國際化資源,finishBeanFactoryInitialization(beanFactory) 實例化非延遲初始化的bean;spring容器初始化的內容仍是很是多的,有興趣的朋友能夠跟着斷點調試詳細看看初始化話過程; 最終所有bean定義都放在了DefaultListableBeanFactory的beanDefinitionMap中了,後續則從beanDefinitionMap中獲取bean定義進行實例化。

  spring mvc容器初始化

    咱們都知道spring mvc的核心類就是DispatcherServlet,咱們就從他入手,以下圖:

    從DispatcherServlet繼承關係可知,HttpServletBean繼承HttpServlet,所以在Web容器啓動時將調用它的init方法,咱們能夠以此爲入口來追蹤DispatcherServlet的初始化過程;DispatcherServlet中的initStrategies方法比較重要,而其中initLocaleResolver(context)和initHandlerMappings(context)和本文的國際化有直接關係,initLocaleResolver(context)將咱們本身定義的localeResolver綁到了DispatcherServlet的屬性localeResolver中;而initHandlerMappings(context)又將咱們本身新增的攔截器LocaleChangeInterceptor添加到了DispatcherServlet的handlerMappings中;

    是否是有種很美妙的預感,咱們自定義的一些bean都關聯到了DispatcherServlet中,而咱們的請求url又必須通過DispatcherServlet,這是否是巧合? 很顯然這不是!若是你仍是一頭霧水,對不起! 咱們接着往下看......

  請求過程

    從DispatcherServlet的繼承關係可知,請求會通過DispatcherServlet的doService方法,doService會將DispatcherServlet中的localeResolver(也就是咱們定義的CookieLocaleResolver對象)綁定到當前request對象中,而後再調用doDispatch進行請求的轉發;

    LocaleChangeInterceptor的類繼承圖

    可知它繼承了HandlerInterceptor,並重寫了preHandle,咱們就從LocaleChangeInterceptor的preHandle方法開始(請求確定會通過此方法)閱讀源碼,打斷點追蹤,以下如

    既然能經過locale參數感知語言的變化,那麼確定也能根據語言加載對應的資源,從而實現國際化(具體如何加載的須要你們本身去閱讀源碼了!)

  源碼閱讀就此告一段落,不是特別細,只是提供了一個主體流程;強烈建議你們閱讀源碼的時候,進行斷點調試跟蹤,不容易跟丟!

參考

  本身動手在Spring-Boot上增強國際化功能

  第三章 DispatcherServlet詳解 ——跟開濤學SpringMVC

相關文章
相關標籤/搜索