所有文章以系列的方式呈现,带领大家成为java高手,目前已出:java高并发系列、mysql高手系列、Maven高手系列、mybatis系列、spring系列,需要PDF版本的,加我微信itsoku获取!

 

前两天去一个电商公司面试:

面试官:Spring中国际化这块的东西用过么?可以介绍一下么?

我:spring中对国际化支持挺好的,比较简单,只需要按照语言配置几个properties文件,然后主要注册一个国际化的相关的bean,同时需指定一下配置文件的位置,基本上就可以了

面试官:那如果配置文件内容有变化?你们怎么解决的?

我:这块啊,spring国际化这块有个实现类,可以检测到配置文件的变化,就可以解决你这个问题

面试官:那我们是否可以将这些国际化的配置丢到db中去管理呢?

我:这个地方我没有搞过,基本上我们这边都是将国际化的配置文件放在项目中的properties文件中;不过以我对spring的理解,spring扩展方面是非常优秀的,应该是可以这么做的,自己去实现一下spring国际化相关接口就可以了。

面试官:工资期望多少?

我:2万

面试官:恭喜你,下周来上班!

为了方便大家,准备把这块知识细化一下,方便大家面试及使用。

本次问题

  1. Spring中国际化怎么用?

  2. 国际化如何处理资源文件变化的问题?

  3. 国际化资源配置放在db中如何实现?

先说一下什么是国际化

简单理解,就是对于不同的语言,做出不同的响应。

比如页面中有个填写用户信息的表单,有个姓名的输入框

浏览器中可以选择语言

选中文的时候会显示:

姓名:一个输入框

选英文的时候会显示:

Full name:一个输入框

国际化就是做这个事情的,根据不同的语言显示不同的信息。

所以需要支持国际化,得先知道选择的是哪种地区的哪种语言,java中使用java.util.Locale来表示地区语言这个对象,内部包含了国家和语言的信息。

Locale中有个比较常用的构造方法

public Locale(String language, String country) {
    this(language, country, "");
}

2个参数:

language:语言

country:国家

语言和国家这两个参数的值不是乱写的,国际上有统一的标准:

比如language的值:zh表示中文,en表示英语,而中文可能很多地区在用,比如大陆地区可以用:CN,新加坡用:SG;英语也是有很多国家用的,GB表示英国,CA表示加拿大

国家语言简写格式:language-country,如:zh-CN(中文【中国】),zh-SG(中文【新加坡】),en-GB(英语【英国】),

en-CA(英语【加拿大】)。

还有很多,这里就不细说了,国家语言编码给大家提供一个表格:http://www.itsoku.com/article/282

Locale类中已经创建好了很多常用的Locale对象,直接可以拿过来用,随便列几个看一下:

static public final Locale SIMPLIFIED_CHINESE = createConstant("zh", "CN"); //zh_CN
static public final Locale UK = createConstant("en", "GB"); //en_GB
static public final Locale US = createConstant("en", "US"); //en_US
static public final Locale CANADA = createConstant("en", "CA"); //en_CA

再回头看前面的问题:页面中显示姓名对应的标签,需要我们根据一个key及Locale信息来获取对应的国际化信息,spring中提供了这部分的实现,下面我们来看详情。

Spring中国际化怎么用?

MessageSource接口

spring中国际化是通过MessageSource这个接口来支持的

org.springframework.context.MessageSource

内部有3个常用的方法用来获取国际化信息,来看一下

public interface MessageSource {

    /**
     * 获取国际化信息
     * @param code 表示国际化资源中的属性名;
     * @param args用于传递格式化串占位符所用的运行参数;
     * @param defaultMessage 当在资源找不到对应属性名时,返回defaultMessage参数所指定的默认信息;
     * @param locale 表示本地化对象
     */
    @Nullable
    String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);

    /**
     * 与上面的方法类似,只不过在找不到资源中对应的属性名时,直接抛出NoSuchMessageException异常
     */
    String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;

    /**
     * @param MessageSourceResolvable 将属性名、参数数组以及默认信息封装起来,它的功能和第一个方法相同
     */
    String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;

}

常见3个实现类

ResourceBundleMessageSource

这个是基于Java的ResourceBundle基础类实现,允许仅通过资源名加载国际化资源

ReloadableResourceBundleMessageSource

这个功能和第一个类的功能类似,多了定时刷新功能,允许在不重启系统的情况下,更新资源的信息

StaticMessageSource

它允许通过编程的方式提供国际化信息,一会我们可以通过这个来实现db中存储国际化信息的功能。

Spring中使用国际化的3个步骤

通常我们使用spring的时候,都会使用带有ApplicationContext字样的spring容器,这些容器一般是继承了AbstractApplicationContext接口,而这个接口实现了上面说的国际化接口MessageSource,所以通常我们用到的ApplicationContext类型的容器都自带了国际化的功能。

通常我们在ApplicationContext类型的容器中使用国际化3个步骤

步骤一:创建国际化文件

步骤二:向容器中注册一个MessageSource类型的bean,bean名称必须为:messageSource

步骤三:调用AbstractApplicationContext中的getMessage来获取国际化信息,其内部将交给第二步中注册的messageSource名称的bean进行处理

来个案例感受一下

创建国际化文件

国际化文件命名格式:名称_语言_地区.properties

我们来3个文件,文件都放在下面这个目录中

com/javacode2018/lesson002/demo19/

message.properties

name=您的姓名
personal_introduction=默认个人介绍:{0},{1}

这个文件名称没有指定Local信息,当系统找不到的时候会使用这个默认的

message_zh_CN.properties:中文【中国】

name=姓名
personal_introduction=个人介绍:{0},{1},{0}

message_en_GB.properties:英文【英国】

name=Full name
personal_introduction=personal_introduction:{0},{1},{0}

spring中注册国际化的bean

注意必须是MessageSource类型的,bean名称必须为messageSource,此处我们就使用ResourceBundleMessageSource这个类

package com.javacode2018.lesson002.test19.demo1;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;

@Configuration
public class MainConfig1 {
    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource result = new ResourceBundleMessageSource();
        //可以指定国际化化配置文件的位置,格式:路径/文件名称,注意不包含【语言_国家.properties】含这部分
        result.setBasenames("com/javacode2018/lesson002/demo19/message"); //@1
        return result;
    }
}

@1:这个地方的写法需要注意,可以指定国际化化配置文件的位置,格式:路径/文件名称,注意不包含【语言_国家.properties】含这部分

来个测试用例

package com.javacode2018.lesson002.test19;

import com.javacode2018.lesson002.test19.demo1.MainConfig1;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Locale;

public class MessageSourceTest {

    @Test
    public void test1() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MainConfig1.class);
        context.refresh();
        //未指定Locale,此时系统会取默认的locale对象,本地默认的值中文【中国】,即:zh_CN
        System.out.println(context.getMessage("name", null, null));
        System.out.println(context.getMessage("name", null, Locale.CHINA)); //CHINA对应:zh_CN
        System.out.println(context.getMessage("name", null, Locale.UK)); //UK对应en_GB
    }
}

运行输出

您的姓名
您的姓名
Full name

第一行未指定Locale,此时系统会取默认的locale对象,本地默认的值中文【中国】,即:zh_CN,所以会获取到message_zh_CN.properties中的内容。

后面2行,都指定了Locale对象,找到对应的国际化文件,取值。

动态参数使用

注意配置文件中的personal_introduction,个人介绍,比较特别,包含了{0},{1},{0}这样一部分内容,这个就是动态参数,调用getMessage的时候,通过第二个参数传递过去,来看一下用法:

@Test
public void test2() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(MainConfig1.class);
    context.refresh();
    //未指定Locale,此时系统会取默认的,本地电脑默认的值中文【中国】,即:zh_CN
    System.out.println(context.getMessage("personal_introduction", new String[]{"spring高手", "java高手"}, Locale.CHINA)); //CHINA对应:zh_CN
    System.out.println(context.getMessage("personal_introduction", new String[]{"spring", "java"}, Locale.UK)); //UK对应en_GB
}

运行输出

默认个人介绍:spring高手,java高手
personal_introduction:spring,java,spring

监控国际化文件的变化

ReloadableResourceBundleMessageSource这个类,功能和上面案例中的ResourceBundleMessageSource类似,不过多了个可以监控国际化资源文件变化的功能,有个方法用来设置缓存时间:

public void setCacheMillis(long cacheMillis)

-1:表示永远缓存

0:每次获取国际化信息的时候,都会重新读取国际化文件

大于0:上次读取配置文件的时间距离当前时间超过了这个时间,重新读取国际化文件

还有个按秒设置缓存时间的方法setCacheSeconds,和setCacheMillis类似

下面我们来案例

package com.javacode2018.lesson002.test19.demo2;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;

@Configuration
public class MainConfig2 {
    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource result = new ReloadableResourceBundleMessageSource();
        result.setBasenames("com/javacode2018/lesson002/demo19/message");
        //设置缓存时间1000毫秒
        result.setCacheMillis(1000);
        return result;
    }
}

message_zh_CN.properties中新增一行内容

address=上海

对应的测试用例

@Test
public void test3() throws InterruptedException {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(MainConfig2.class);
    context.refresh();
    //输出2次
    for (int i = 0; i < 2; i++) {
        System.out.println(context.getMessage("address", null, Locale.CHINA));
        TimeUnit.SECONDS.sleep(5);
    }
}

上面有个循环,当第一次输出之后,修改一下message_zh_CN.properties中的address为上海松江,最后运行结果如下:

上海
上海松江

使用注意:线上环境,缓存时间最好设置大一点,性能会好一些。

国际化信息存在db中

上面我们介绍了一个类:StaticMessageSource,这个类它允许通过编程的方式提供国际化信息,我们通过这个类来实现从db中获取国际化信息的功能。

这个类中有2个方法比较重要:

public void addMessage(String code, Locale locale, String msg);
public void addMessages(Map<String, String> messages, Locale locale);

通过这两个方法来添加国际化配置信息。

下面来看案例

自定义一个StaticMessageSource类

package com.javacode2018.lesson002.test19.demo3;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.support.StaticMessageSource;

import java.util.Locale;

public class MessageSourceFromDb extends StaticMessageSource implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        //此处我们在当前bean初始化之后,模拟从db中获取国际化信息,然后调用addMessage来配置国际化信息
        this.addMessage("desc", Locale.CHINA, "我是从db来的信息");
        this.addMessage("desc", Locale.UK, "MessageSource From Db");
    }
}

上面的类实现了spring的InitializingBean接口,重写了接口中干掉afterPropertiesSet方法,这个方***在当前bean初始化之后调用,在这个方法中模拟从db中获取国际化信息,然后调用addMessage来配置国际化信息

来个spring配置类,将MessageSourceFromDb注册到spring容器

package com.javacode2018.lesson002.test19.demo3;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MainConfig3 {
    @Bean
    public MessageSource messageSource(){
        return new MessageSourceFromDb();
    }
}

上测试用例

@Test
public void test4() throws InterruptedException {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(MainConfig3.class);
    context.refresh();
    System.out.println(context.getMessage("desc", null, Locale.CHINA));
    System.out.println(context.getMessage("desc", null, Locale.UK));
}

运行输出

我是从db来的信息
MessageSource From Db

bean名称为什么必须是messageSource

上面我容器启动的时候会调用refresh方法,过程如下:

org.springframework.context.support.AbstractApplicationContext#refresh
内部会调用
org.springframework.context.support.AbstractApplicationContext#initMessageSource
这个方法用来初始化MessageSource,方法内部会查找当前容器中是否有messageSource名称的bean,如果有就将其作为处理国际化的对象
如果没有找到,此时会注册一个名称为messageSource的MessageSource

自定义bean中使用国际化

自定义的bean如果想使用国际化,比较简单,只需实现下面这个接口,spring容器会自动调用这个方法,将MessageSource注入,然后我们就可以使用MessageSource获取国际化信息了。

public interface MessageSourceAware extends Aware {
    void setMessageSource(MessageSource messageSource);
}

总结

本文介绍了国际化的使用,涉及到了java中的Locale类,这个类用来表示语言国家信息,获取国际化信息的时候需要携带这个参数,spring中通过MessageSource接口来支持国际化的功能,有3个常用的实现类需要了解,StaticMessageSource支持硬编码的方式配置国际化信息。

如果需要spring支撑国际化,需要注册一个bean名称为messageSource的MessageSource,这个一定要注意。

到此,上面面试的3个问题,大家都能轻松应对了。

前言

Spring是一个开源框架,它由Rod Johnson创建。它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。

Spring框架自诞生以来一直备受开发者青睐,今天在这里分享的是一套Spring面试专题集合。其中包括了Spring、SpringBoot、SpringCloud、SpringMVC四个面试专题文档,都是经过BAT面试实战精选过的重点内容。

需要的朋友帮忙转发+关注然后私信“Spring”获得完整面试文档的领取方式

以下为 spring 常见面试问题:

什么是 Spring 框架?Spring 框架有哪些主要模块

使用 Spring 框架能带来哪些好处

什么是控制反转(IOC)?什么是依赖注入

请解释下 Spring 框架中的 IoC

BeanFactory 和 ApplicationContext 有什么区别

Spring 有几种配置方式

如何用基于 XML 配置的方式配置 Spring

如何用基于 Java 配置的方式配置 Spring

怎样用注解的方式配置 Spring

请解释 Spring Bean 的生命周期

Spring Bean 的作用域之间有什么区别

什么是 Spring inner beans

Spring 框架中的单例 Beans 是线程安全的么

请举例说明如何在 Spring 中注入一个 Java Collection

如何向 Spring Bean 中注入一个 Java.util.Properties

请解释 Spring Bean 的自动装配

请解释自动装配模式的区别

如何开启基于注解的自动装配

请举例解释@Required 注解

请举例解释@Autowired 注解

请举例说明@Qualifier 注解

构造方法注入和设值注入有什么区别

Spring 框架中有哪些不同类型的事件

FileSystemResource 和 ClassPathResource 有何区别

Spring 框架中都用到了哪些设计模式

面试专题与答案

SpringBoot面试专题

Spring Boot 的优点有

什么是 JavaConfig

如何重新加载 Spring Boot 上的更改,而无需重新启动服务器

Spring Boot 中的监视器是什么

如何在 Spring Boot 中禁用 Actuator 端点安全性

如何在自定义端口上运行 Spring Boot 应用程序

什么是 YAML

如何实现 Spring Boot 应用程序的安全性

如何集成 Spring Boot 和 ActiveMQ

如何使用 Spring Boot 实现分页和排序

什么是 Swagger?你用 Spring Boot 实现了它吗

什么是 Spring Profiles

什么是 Spring Batch

什么是 FreeMarker 模板

如何使用 Spring Boot 实现异常处理

您使用了哪些 starter maven 依赖项

什么是 CSRF 攻击

什么是 WebSockets

什么是 AOP

什么是 Apache Kafka

我们如何监视所有 Spring Boot 微服务

springboot专题与答案

SpringCloud面试专题

使用 Spring Cloud 有什么优势

服务注册和发现是什么意思?Spring Cloud 如何实现

负载平衡的意义什么

什么是 Hystrix?它如何实现容错

什么是 Hystrix 断路器?我们需要它吗

什么是 Netflix Feign?它的优点是什么

什么是 Spring Cloud Bus?我们需要它吗

spring Cloud专题与答案

SpringMVC面试专题

什么是 SpringMvcSpring MVC 的优点

SpringMVC 工作原理

SpringMVC 流程

SpringMvc 的控制器是不是单例模式,如果是,有什么问题,怎么解决

如果你也用过 struts2.简单介绍下 springMVC 和 struts2 的区别有哪些

SpingMvc 中的控制器的注解一般用那个,有没有别的注解可以替代

@RequestMapping 注解用在类上面有什么作用

怎么样把某个请求映射到特定的方法上面

如果在拦截请求中,我想拦截 get 方式提交的方法,怎么配置

怎么样在方法里面得到 Request,或者 Session

我想在拦截的方法里面得到从前台传入的参数,怎么得到

如果前台有很多个参数传入,并且这些参数都是一个对象的,那么怎么样快速得到这个对象

SpringMvc 中函数的返回值是什么

SpringMVC 怎么样设定重定向和转发的

SpringMvc 用什么对象从后台向前台传递数据的

SpringMvc 中有个类把视图和数据都合并的一起的,叫什么

怎么样把 ModelMap 里面的数据放入 Session 里面

SpringMvc 怎么和 AJAX 相互调用的

当一个方法向 AJAX 返回特殊对象,譬如 Object,List 等,需要做什么处理

SpringMvc 里面拦截器是怎么写的

讲下 SpringMvc 的执行流程

SpringMvc专题与答案

相比于前几年来说,现在的面试难度提升了不少。你说说现在程序员这么多,你投递的公司可能与你一起投递的就有300人,可人家只招5人,那怎么办?简历PASS掉一大批,然后面试再PASS掉一大批。从这样来看,现在流行说的:“面试造火箭,工作拧螺丝”还是很有道理的,提高面试难度只是为了从很多人中招到自己需要的那些人。

获取方法:转发+关注然后私信“spring”得到完整面试文档的领取方式