深入 java 系列之国际化

Posted by NotGeek on December 26, 2018

深入 JAVA 系列之国际化

现在很多面试题比较烂。

比如说, HashMap

在 JAVA 8 中的 红黑树的引用,对于你代码的提升,其实是没有太多的感受的。

本期议题

  • JAVA SE 国际化应用
  • JAVA EE 国际化应用
  • Spring 国际化应用

Java SE 国际化应用

  • Locale
  • ResourceBundle
    • ClassLoader
    • Control@since Java 6
    • ResourceBundleControlProvider@since Java 8
  • Format

Java EE 国际化应用

  • Servlet
    • ServletRequest
  • JSP
  • JSTL
  • JSF(JSR-252)
  • Bean Validation(JSR-303)

Spring 国际化应用

  • Java SE
    • MessageSource
  • Java EE
    • DispatcherServlet
    • LocaleContextHolder
    • LocaleResolver

Spring 是一个伟大的重复发明轮子。多了解 JAVA 规范以后,会对 Spring 的理解会加深很多,真正的 JAVA 高手,还是对底层非常的熟悉。包括 JDK 源码,和 JSR 规范。

1
Spring 如何利用 JAVA 的规范?

JAVA SE 国际化应用

  • Locale
  • ResourceBundle
    • ClassLoader
    • Control@since Java 6
    • ResourceBundleControlProvider@since Java 8
  • Format
1
我们很多时候,在大多数场景使用很多很高级的框架,Spring 、Spring boot、Netty、Hadhoop ,但是我们对底层不是很了解。
  • Language Country variant(语言,地区,语言变种类似于方言)

国内或是国际上一些把 Spring 讲清楚的人都很少。JAVA 更少。

1
2
3
4
5
6
7
8
9
/***
 * {@link java.util.Locale}
 */
public class LocaleDemo {
    public static void main(String[] args) {
        // 输入默认的 Locale
        System.out.println(Locale.getDefault());
    }
}
1
zh_CN

和系统的语言跟时区有关系,是国际化的标准。

我本地的 Locale 可不可以改?

java.util.Locale#Locale(java.lang.String, java.lang.String, java.lang.String)

1
2
3
4
5
6
7
public Locale(String language, String country, String variant) {
    if (language== null || country == null || variant == null) {
        throw new NullPointerException();
    }
    baseLocale = BaseLocale.getInstance(convertOldISOCodes(language), "", country, variant);
    localeExtensions = getCompatibilityExtensions(language, "", country, variant);
}

语言是和操作系统一脉相承。

  • user.language en
  • user.region

  • 输入默认的 Locale
  • 硬编码调整 en_US,无法做到一份代码,到处运行。
  • 通过启动参数调整 -Dxxx 等同于 System.setProperty();

System.setProperty() 可能更改不了与 安全 有关系。

  • 安全
    • PropertyPermission

Integer 的缓存,低版本是可以调整的。

1545803032671

  • default.properties

    1
    2
    
    # 默认配置
    name=darian
    
  • default_zh_CN.properties

    1
    2
    
    # 简体中文配置
    name=小诸葛
    
代码
1
2
3
4
5
6
7
8
9
10
11
/***
 * {@link java.util.ResourceBundle}
 */
public class ResourceBundleDemo {
    public static void main(String[] args) {
        // pachage(目录) + resource 名称(不包含 properties)
        String baseName= "static.default";
        ResourceBundle bundle = ResourceBundle.getBundle(baseName);
        System.out.println("name: " + bundle.getString("name"));
    }
}
1
2
name: 小诸葛

乱码解决方案

  • -Dfile.encoding=UTF-8 无效的设置 native2ascii \uxxx -> Unicode
  • Control

native2ascii 能够将编码转化为 Unicode 编码。

每种技术的衍进肯定是要解决以前的一些技术的不足。

  • 功能上的不足
  • 便利上的不足

java.util.ResourceBundle.Control

1
2
3
4
5
6
public static class Control {

    public static final List<String> FORMAT_DEFAULT = List.of("java.class", "java.properties");
    
}

不仅支持 .properties 还支持 .class

我们不得不写一个类,去做这么一个事情。

1
2
3
4
5
6
7
8
9
10
11
12
/***
 * {@link java.util.ResourceBundle}
 */
public class ResourceBundleDemo {
    public static void main(String[] args) {
        // pachage(目录) + resource 名称(不包含 properties)
        String baseName= "static.default";
        ResourceBundle bundle = ResourceBundle.getBundle(baseName, Locale.ENGLISH);
        System.out.println("name: " + bundle.getString("name"));
    }
}

1545844176477

  • properties 加载步骤
    • 先找 default_en.properties
    • 找不到,再找系统的语言 default_zh_CN.properties
    • 最后再找 default.properties

Since 1.6 ResourceBundle.Control

java.util.ResourceBundle.Control#getControl

1
2
3
4
5
6
7
8
9
10
11
12
13
public static final Control getControl(List<String> formats) {
    if (formats.equals(Control.FORMAT_PROPERTIES)) {
        return SingleFormatControl.PROPERTIES_ONLY;
    }
    if (formats.equals(Control.FORMAT_CLASS)) {
        return SingleFormatControl.CLASS_ONLY;
    }
    if (formats.equals(Control.FORMAT_DEFAULT)) {
        return Control.INSTANCE;
    }
    throw new IllegalArgumentException();
}

不同的调用不同的 Control

乱码怎么来的?

java.util.ResourceBundle.Control#newBundle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public ResourceBundle newBundle(String baseName, Locale locale, String format,
                                ClassLoader loader, boolean reload)
            throws IllegalAccessException, InstantiationException, IOException {
    /*
     * Legacy mechanism to locate resource bundle in unnamed module only
     * that is visible to the given loader and accessible to the given caller.
     */
    String bundleName = toBundleName(baseName, locale);
    ResourceBundle bundle = null;
    if (format.equals("java.class")) {
        //....
        // 这里是 java.class 文件格式的加载。。。
        //.....
    } else if (format.equals("java.properties")) {
        final String resourceName = toResourceName0(bundleName, "properties");
        if (resourceName == null) {
            return bundle;
        }

        final boolean reloadFlag = reload;
        InputStream stream = null;
        try {
            stream = AccessController.doPrivileged(
                new PrivilegedExceptionAction<>() {
                    public InputStream run() throws IOException {
                        URL url = loader.getResource(resourceName);
                        if (url == null) return null;

                        URLConnection connection = url.openConnection();
                        if (reloadFlag) {
                            // Disable caches to get fresh data for
                            // reloading.
                            connection.setUseCaches(false);
                        }
                        return connection.getInputStream();
                    }
                });
        } catch (PrivilegedActionException e) {
            throw (IOException) e.getException();
        }
        if (stream != null) {
            try {
                bundle = new PropertyResourceBundle(stream);
            } finally {
                stream.close();
            }
        }
    } else {
        throw new IllegalArgumentException("unknown format: " + format);
    }
    return bundle;
}

  • 这又和 ClassLoader 有关系
  • 它是通过 InputStream 去读取
  • PropertyResourceBundleResourceBundle 的一个实现
    • PropertyResourceBundle#PropertyResourceBundle(InputStream)
java.util.PropertyResourceBundle
1
2
3
4
5
6
7
8
9
10
11
12
13
// Check whether the strict encoding is specified.
// The possible encoding is either "ISO-8859-1" or "UTF-8".
private static final String encoding = GetPropertyAction
        .privilegedGetProperty("java.util.PropertyResourceBundle.encoding", "")
    .toUpperCase(Locale.ROOT);

/***
  * JDK 11
  */
public PropertyResourceBundle (InputStream stream) throws IOException {
    this(new InputStreamReader(stream, "ISO-8859-1".equals(encoding) ?                               StandardCharsets.ISO_8859_1.newDecoder() : new PropertyResourceBundleCharset("UTF-8".equals(encoding)).newDecoder()));
}

解决方法1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ResourceBundleDemo {
    public static void main(String[] args) throws IOException {
        // pachage(目录) + resource 名称(不包含 properties)
        String baseName= "static.default";
        ResourceBundle bundle = ResourceBundle.getBundle(baseName, Locale.ENGLISH);
        System.out.println("[bundle] name: " + bundle.getString("name"));

        ClassLoader classLoader = ResourceBundleDemo.class.getClassLoader();
        InputStream inputStream = classLoader.getResourceAsStream("static/default_zh_CN.properties");
        InputStreamReader reader = new InputStreamReader(inputStream, "UTF-8");
        PropertyResourceBundle propertyResourceBundle = new PropertyResourceBundle(reader);
        System.out.println("[PropertyResourceBundle] name:"+ propertyResourceBundle.getString("name"));
    }
}

1
2
3
[bundle] name: darian_en
[PropertyResourceBundle] name:小诸葛

  • 扩展 Control
  • 重写 Resourcebundle#newBundle 方法

native2ascii 可以解决乱码问题。

  • 局限: 它不能直接作用于你的源代码。只能作用于你的 .class 文件。

我可以通过重载覆盖默认的方法 ResourceBundle.Control 的构造方法一劳永逸的解决乱码问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/***
 * {@link ResourceBundle}
 */
public class CustomerResourceBundleDemo {
    public static void main(String[] args) throws IOException {
        // pachage(目录) + resource 名称(不包含 properties)
        String baseName= "static.default";
        ResourceBundle bundle = ResourceBundle.getBundle(baseName, new EncodedControl("UTF-8"));
        System.out.println("[bundle] name: " + bundle.getString("name"));


    }
    private static void PropertyResourceBundleRender() throws IOException {
        ClassLoader classLoader = CustomerResourceBundleDemo.class.getClassLoader();
        InputStream inputStream = classLoader.getResourceAsStream("static/default_zh_CN.properties");
        InputStreamReader reader = new InputStreamReader(inputStream, "UTF-8");
        PropertyResourceBundle propertyResourceBundle = new PropertyResourceBundle(reader);
        System.out.println("[PropertyResourceBundle] name:"+ propertyResourceBundle.getString("name"));
    }

    public static class EncodedControl extends ResourceBundle.Control{

        private final String encoding;

        public EncodedControl(String encoding) {
            this.encoding = encoding;
        }

        @Override
        public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) throws IllegalAccessException, InstantiationException, IOException {
            String bundleName = toBundleName(baseName, locale);
            ResourceBundle bundle = null;
            if (format.equals("java.properties")) {
                final String resourceName = toResourceName(bundleName, "properties");
                if (resourceName == null) {
                    return bundle;
                }

                final boolean reloadFlag = reload;
                InputStream stream = null;
                try {
                    stream = AccessController.doPrivileged(
                            new PrivilegedExceptionAction<>() {
                                public InputStream run() throws IOException {
                                    URL url = loader.getResource(resourceName);
                                    if (url == null) return null;

                                    URLConnection connection = url.openConnection();
                                    if (reloadFlag) {
                                        // Disable caches to get fresh data for
                                        // reloading.
                                        connection.setUseCaches(false);
                                    }
                                    return connection.getInputStream();
                                }
                            });
                } catch (PrivilegedActionException e) {
                    throw (IOException) e.getException();
                }
                Reader reader = new InputStreamReader(stream, encoding);
                if (reader != null) {
                    try {
                        bundle = new PropertyResourceBundle(reader);
                    } finally {
                        reader.close();
                        stream.close();
                    }
                }
            } else {
                throw new IllegalArgumentException("unknown format: " + format);
            }
            return bundle;
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static Locale initDefault() {
 String language, region, script, country, variant;
 Properties props = GetPropertyAction.privilegedGetProperties();
 language = props.getProperty("user.language", "en");
 // for compatibility, check for old user.region property
 region = props.getProperty("user.region");
 if (region != null) {
     // region can be of form country, country_variant, or _variant
     int i = region.indexOf('_');
     if (i >= 0) {
         country = region.substring(0, i);
         variant = region.substring(i + 1);
     } else {
         country = region;
         variant = "";
     }
     script = "";
 } else {
     script = props.getProperty("user.script", "");
     country = props.getProperty("user.country", "");
     variant = props.getProperty("user.variant", "");
 }

 return getInstance(language, script, country, variant,
         getDefaultExtensions(props.getProperty("user.extensions", ""))
             .orElse(null));
}

总计

  • JAVA 通过 ResourceBundle 实现,查找 properties 文件中的国际化内容。

  • PropertyResourceBundle JDK 11 以后 如果设置了 ISO-8859-1 就是 ISO-8859-1 否则,就是 UTF-8 .

  • 可以 采用 native2ascii 方法,将打包后的资源文件进行转移,而不是直接在源码方面解决。

    我们如果项目很多人,张三搞一把,李四搞一把,如果有一个小朋友没有搞,那么项目就会乱码了。

  • 而重写 ResourceBundle.Control 的方法需要基于 JDK 1.6 以上, 如果 没有 jdk 1.6 只能通过 native2ascii。

    • 缺点: 可移植性不强,你每次都需要去 显式 传递 new EncodedControl("UTF-8") ;
  • since JDK 1.8 实现 java.util.spi.ResourceBundleControlProvider ,java 8 有 ResourceBundleControlProvider SPI

mercyblitz 的 实现: PropertyResourceBundleControl

java.util.ResourceBundle java 11

1
2
private static ServiceLoader<ResourceBundleProvider> getServiceLoader(Module module,
                                                                      String baseName)

我们不能保证每个人都传递 new Control(XXX)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/***
 * {@link ResourceBundle}
 */
public class CustomerResourceBundleDemo {
    public static void main(String[] args) throws IOException {
        // pachage(目录) + resource 名称(不包含 properties)
        String baseName= "static.default";

        // 显示的传递 EncodedControl
        ResourceBundle bundle = ResourceBundle.getBundle(baseName, new EncodedControl("UTF-8"));
        System.out.println("[bundle] name: " + bundle.getString("name"));

        // 使用默认的 ResourceBundleControlProvider SPI 机制
        bundle = ResourceBundle.getBundle(baseName);
        System.out.println("[ResourceBundleControlProvider] name: " + bundle.getString("name"));
    }

    public static class EncodedControl extends ResourceBundle.Control{

        private final String encoding;

        public EncodedControl(String encoding) {
            this.encoding = encoding;
        }

        public EncodedControl() {
            this("UTF-8");
        }

        @Override
        public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) throws IllegalAccessException, InstantiationException, IOException {
           // 。。。。。。
    }
}
1
2
3
4
5
6
7
public class EncodingResourceBundleControlProvider implements ResourceBundleControlProvider {

    @Override
    public ResourceBundle.Control getControl(String baseName) {
        return new CustomerResourceBundleDemo.EncodedControl();
    }
}

1545884791869

1
2
[bundle] name: 小诸葛
[ResourceBundleControlProvider] name: 小诸葛

一个工程师,或者说架构师,要了解设计,API 的设计,对于设计模式也好,通用的框架也好,真的很重要。

1
2
3
好的设计可以提高开发效率。不好的设计,一坑十,十坑百。

要重视 API 的设计,比如说 API 的设计。
  • JAVA 的 SPI 是没有名称,控制不了顺序。
  • Dubbo 的 SPI 利用的是 key, value 的方式来做。

Dubbo 和 JAVA 的 SPI 各有利弊,不要说如何如何好,要考虑到不好,才是关键。

Format

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/***
 * {@link java.text.SimpleDateFormat}
 */
public class DateFormatDemo implements Runnable{
    public static void main(String[] args) {
        new Thread(new DateFormatDemo()).start();
        new Thread(new DateFormatDemo()).start();
    }

    @Override
    public void run() {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        System.out.println(dateFormat.format(new Date()));
    }
}

这个代码是线程安全的。

什么叫重进入。

java.util.concurrent.locks.ReentrantLock

一个方法不停的进入,是线程安全的,因为数据 dateFormat 没有暴漏出去,所以它是线程安全的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/***
 * {@link java.text.SimpleDateFormat}
 */
public class DateFormatDemo implements Runnable {
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    public static void main(String[] args) {
        new Thread(new DateFormatDemo()).start();
        new Thread(new DateFormatDemo()).start();
    }

    @Override
    public void run() { // 重进入
        System.out.println(dateFormat.format(new Date()));
    }
}

这样也没问题,因为你每次都是 new 的新的对象,也没问题。

只有你这个对象,能够被所有的线程所看到,然后同时读和写的时候,才是线程不安全的。

1
 理解深刻,就不会有那么多问题了。
1
2
3
4
5
6
7
8
9
10
11
/***
 *{@link java.text.NumberFormat} 实例
 */
public class NumberFormatDemo {
    public static void main(String[] args) {
        NumberFormat numberFormat = NumberFormat.getNumberInstance();
        System.out.println(numberFormat.format(10000));
        numberFormat = NumberFormat.getNumberInstance(Locale.FRANCE);
        System.out.println(numberFormat.format(10000));
    }
}
1
2
10,000
10 000

1545886875752

当你发现 new 方法报错的时候,你就要思考,它是不是抽象类。那么它必然会有很多实现类,还有静态工厂方法。

windows 的数字格式等等,都可以去设置。

Locale 只是一个入口,只是一个 API ,它控制了非常多的东西,比如说:

ResourceBundleFormat 国际化的运用很多

你就不会发愁 国际货币的表达方式了。

基础很重要。只研究高层框架,很难发展。

三个方法:

  • native2ascii
  • 而重写 ResourceBundle.Control since 1.6
  • 实现 java.util.spi.ResourceBundleControlProvider since JDK 1.8 SPI

JAVA EE 国际化的运用

servlet 运用

培养技术的能力不能靠面试。面试只能告诉你市场上的需求,但是如果没有一个背景的话,你是不可能理解的很透彻的。

1
2
3
不要太浮躁,要注重积累。

Spring 的设计,很简单的。
1
2
3
4
5
6
7
8
9
10
11
12
@WebServlet(urlPatterns = "/local")
public class LocaleServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        Locale locale = request.getLocale();
        long num = Long.parseLong(request.getParameter("num"));
        Format format = NumberFormat.getNumberInstance(locale);
        response.getWriter().println(format.format(num));
    }
}

1545888365945

通过切换,语言,实现不同的格式。

1545888710975

这是 HTTP 协议的规范。

windows 也可以设置,这是国际的通用标准。

JSTL

1545888993252

它是串起来的。

JSF

JavaServer Faces Specification

1545889228000

本地化和国际化。

国际化是对服务端而言的。本地化是对用户而言的。

Bean Validation

1545889447559

1
2
3
4
5
public interface MessageInterpolator{
//。。。。。。
String interpolate(String messageTemplate, Context context, Locale locale);
//。。。。。。
}

我们可以进行国际化的设置。

1545889499153

javax.validation.constraints.NotNull.message

1545889605168

同一个文案可以让你在不同的 properties 里边有, JSR 303 都是允许你去拓展的。

Spring

org.springframework.context.MessageSource

1
2
3
4
5
6
7
8
9
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;

}

传进来 Locale

MessageFormatDemo
1
2
3
4
5
6
7
8
9
10
11
/***
 * {@link java.text.MessageFormat}
 */
public class MessageFormatDemo {
    public static void main(String[] args) {
        // Formatter 是 JAVA 5 里边的。
        MessageFormat format = new MessageFormat("Hello,{0} , {1}!");

        System.out.println(format.format(new String[]{"world","darian"}));
    }
}
1
Hello,world , darian!
ResourceBundleMessageSourceDemo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/***
 * {@link org.springframework.context.support.ResourceBundleMessageSource} 实例
 */
public class ResourceBundleMessageSourceDemo {
    public static void main(String[] args) {
        String baseName = "static.default";
        // ResourceBundle + MessageFormat > MessageSource
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename(baseName);
        System.out.println(messageSource.getMessage("message",
                new Object[]{"darian"},
                Locale.CHINA));
    }
}

Spring 的 MessageSource 就是重复发明轮子。

DispatcherServlet 它的国际化是怎么去处理的?

org.springframework.web.servlet.View

1
2
3
4
5
6
7
8
9
10
11
12
public interface View {
    String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
    String PATH_VARIABLES = View.class.getName() + ".pathVariables";
    String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";

    @Nullable
    default String getContentType() {
        return null;
    }

    void render(@Nullable Map<String, ?> var1, HttpServletRequest var2, HttpServletResponse var3) throws Exception;
}

渲染上下文是知道我的编码的。

org.springframework.web.servlet.LocaleResolver

1
2
3
4
5
public interface LocaleResolver {
    Locale resolveLocale(HttpServletRequest var1);

    void setLocale(HttpServletRequest var1, @Nullable HttpServletResponse var2, @Nullable Locale var3);
}

我要设置这个 Locale

org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class AcceptHeaderLocaleResolver implements LocaleResolver {
    @Nullable
    private Locale defaultLocale;
    public Locale resolveLocale(HttpServletRequest request) {
        Locale defaultLocale = this.getDefaultLocale();
        if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
            return defaultLocale;
        } else {
            Locale requestLocale = request.getLocale();
            List<Locale> supportedLocales = this.getSupportedLocales();
            if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
                Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
                if (supportedLocale != null) {
                    return supportedLocale;
                } else {
                    return defaultLocale != null ? defaultLocale : requestLocale;
                }
            } else {
                return requestLocale;
            }
        }
    }
}
@Nullable
public Locale getDefaultLocale() {
    return this.defaultLocale;
}

会有很多很多请求的实现。

还可能放到 cookie 的实现,

我是在美国,我的语言设置成中文,我用 Locale 来操作。我用 LocaleContextResolver 有很多实现,我可以实现自动的装配。

如何当前的 Locale 是一定的呢?需要和 ThreadLocal 结合。

1
2
3
4
5
6
7
public final class LocaleContextHolder {

   private static final ThreadLocal<LocaleContext> localeContextHolder =
         new NamedThreadLocal<>("LocaleContext");

   private static final ThreadLocal<LocaleContext> inheritableLocaleContextHolder =
         new NamedInheritableThreadLocal<>("LocaleContext");

每次都去里边去存。

org.springframework.context.i18n.LocaleContext

1
2
3
4
public interface LocaleContext {
   @Nullable
   Locale getLocale();
}

每个线程都去里边取出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void initLocaleResolver(ApplicationContext context) {
    try {
        this.localeResolver = (LocaleResolver)context.getBean("localeResolver", LocaleResolver.class);
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Detected " + this.localeResolver);
        } else if (this.logger.isDebugEnabled()) {
            this.logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
        }
    } catch (NoSuchBeanDefinitionException var3) {
        this.localeResolver = (LocaleResolver)this.getDefaultStrategy(context, LocaleResolver.class);
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("No LocaleResolver 'localeResolver': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
        }
    }

}

我们在这里把 LocaleResolver 进行实例化。

Spring MVC 实现了多种实现。对视图。
1
我们页面也可能是国际化的。我们页面模板相同的时候。我们可以替换页面。我们当前的页面在不同的国家,不同的一样。处理页面的时候,我会把 `Locale` 去传进去。