Spring官网阅读(十一)ApplicationContext详细介绍(上)(1)

简介: 在前面的文章中,我们已经完成了官网中关于IOC内容核心的部分。包括容器的概念,Spring创建Bean的模型BeanDefinition的介绍,容器的扩展点(BeanFactoryPostProcessor,FactroyBean,BeanPostProcessor)以及最重要的Bean的生命周期等。接下来大概还要花三篇文章完成对官网中第一大节的其它内容的学习,之所以要这么做,是笔者自己粗读了一篇源码后,再读一遍官网,发现源码中的很多细节以及难点都在官网中介绍了。所以这里先跟大家一起把官网中的内容都过一遍,也是为了更好的进入源码学习阶段。本文主要涉及到官网中的1.13,1.15,1.16小

ApplicationContext


1、ApplicationContext的继承关系


微信图片_20221112191037.png

从上图中可以发现,ApplicationContext接口继承了很多接口,这些接口我们可以将其分为五类:


  • MessageSource,主要用于国际化
  • ApplicationEventPublisher,提供了事件发布功能
  • EnvironmentCapable,可以获取容器当前运行的环境
  • ResourceLoader,主要用于加载资源文件
  • BeanFactory,负责配置、创建、管理Bean,IOC功能的实现主要就依赖于该接口子类实现。

关于这些的接口的具体功能的介绍在后文会介绍,当前我们需要知道最重要的一点就是ApplicationContext继承了BeanFactory接口,也就是说它具有BeanFactory所有的功能。


2、ApplicationContext的功能


Spring中的国际化(MessageSource)


国际化是什么?


应用程序运行时,可根据客户端操作系統的国家/地区、语言的不同而显示不同的界面,比如客户端OS的语言环境为大陆的简体中文,程序就显示为简体中文,客户端OS的语言环境为美国——英语,程序就显示美式英语。OS的语言环境可在控制面板中手动设置。国际化的英文单词是Internationalization,单词较长,通常简称i18n,I是第一个字母,18表示中间省略了18个字母,N是最后一个字母。


假设我们正在开发一个支持多国语言的Web应用程序,要求系统能够根据客户端的系统的语言类型返回对应的界面:英文的操作系统返回英文界面,而中文的操作系统则返回中文界面——这便是典型的i18n国际化问题。对于有国际化要求的应用系统,我们不能简单地采用硬编码的方式编写用户界面信息、报错信息等内容,而必须为这些需要国际化的信息进行特殊处理。简单来说,就是为每种语言提供一套相应的资源文件,并以规范化命名的方式保存在特定的目录中,由系统自动根据客户端语言选择适合的资源文件。


JAVA中的国际化


国际化信息也称为本地化信息,一般需要两个条件才可以确定一个特定类型的本地化信息,它们分别是“语言类型”和“国家/地区的类型”。如中文本地化信息既有中国大陆地区的中文,又有中国台湾、中国香港地区的中文,还有新加坡地区的中文。


【部分国际化代码】:

ar_sa 阿拉伯语(沙特阿拉伯)
ar_iq 阿拉伯语(伊拉克) 
eu 巴斯克语
bg 保加利亚语 
zh_tw 中文(中国台湾)
zh_cn 中文(中华人民共和国)
zh_hk 中文(中国香港特别行政区)
zh_sg 中文(新加坡)
hr 克罗地亚语 
en 英语
en_us 英语(美国)
en_gb 英语(英国)
en_au 英语(澳大利亚)
en_ca 英语(加拿大)

本地化对象(Locale)

Java通过java.util.Locale类表示一个本地化对象,它允许通过语言参数和国家/地区参数创建一个确定的本地化对象。

Locale locale=new Locale("zh","cn");//中文,中国
Locale locale2=new Locale("en","us");//英文,美国
Locale locale3=new Locale("zh");//中文--不指定国家
Locale locale4=Locale.CHINA;//中文,中国
Locale locale5=Locale.CHINESE;//中文

在持有一个Locale对象后,我们需要将同一个文字或者数字根据不同的地区/语言格式化成不同的表现形式,所以这里我们还需要一个格式化的操作,JDK给我们提供以下几个常见的类用于国际化格式化

NumberFormat:可以处理数字,百分数,货币等。下面以货币为例:

public static void main(String[] args) {
    // 1.通过语言跟地区确定一个Locale对象
    // 中国,中文
    Locale chinaLocale = new Locale("zh", "cn");
    // 美国,英文
    Locale usLocale = new Locale("en", "us");
    // 获取货币格式化对象
    NumberFormat chinaCurrencyFormat = NumberFormat.getCurrencyInstance(chinaLocale);
    NumberFormat usLocaleCurrencyFormat = NumberFormat.getCurrencyInstance(usLocale);
    // 中文,中国下的货币表现形式
    String chinaCurrency = chinaCurrencyFormat.format(99.9);  // 输出 ¥99.90
    // 美国,英文下的货币表现形式
    String usCurrency = usLocaleCurrencyFormat.format(99.9); // 输出 $99.90
    System.out.println(chinaCurrency);
    System.out.println(usCurrency);
}

格式化对象

DateFormat:通过DateFormat#getDateInstance(int style,Locale locale)方法按本地化的方式对日期进行格式化操作。该方法第一个入参为时间样式,第二个入参为本地化对象

public static void main(String[] args) {
    // 1.通过语言跟地区确定一个Locale对象
    // 中国,中文
    Locale chinaLocale = new Locale("zh", "cn");
    // 美国,英文
    Locale usLocale = new Locale("en", "us");
    DateFormat chinaDateFormat = DateFormat.getDateInstance(DateFormat.YEAR_FIELD,chinaLocale);
    DateFormat usDateFormat = DateFormat.getDateInstance(DateFormat.YEAR_FIELD,usLocale);
    System.out.println(chinaDateFormat.format(new Date())); // 输出 2020年1月15日
    System.out.println(usDateFormat.format(new Date()));    // 输出 January 15, 2020
}

MessageFormat:在NumberFormat和DateFormat的基础上提供了强大的占位符字符串的格式化功能,它支持时间、货币、数字以及对象属性的格式化操作

1.简单的占位符替换

public static void main(String[] args) {
    // 1.通过语言跟地区确定一个Locale对象
    // 中国,中文
    Locale chinaLocale = new Locale("zh", "cn");
    String str1 = "{0},你好!你于{1}在农业银行存入{2}。";
    MessageFormat messageFormat = new MessageFormat(str1,chinaLocale);
    Object[] o = {"小红", new Date(), 99.99};
    System.out.println(messageFormat.format(o));
    // 输出:小红,你好!你于20-1-15 下午4:05在农业银行存入99.99。
}

1.指定格式化类型跟格式化样式的占位符替换

public static void main(String[] args) {
    String str1 = "{0},你好!你于{1,date,long}在农业银行存入{2,number, currency}。";
    MessageFormat messageFormat = new MessageFormat(str1,Locale.CHINA);
    Object[] o = {"小红", new Date(), 1313};
    System.out.println(messageFormat.format(o));
    // 输出:小红,你好!你于2020年1月15日在农业银行存入¥1,313.00。
}

在上面的例子中,0,1,2代表的是占位符的索引,从0开始计数。date,number为格式化的类型。long,currency为格式化样式。


  • FormatType:格式化类型,取值范围如下:

number:调用NumberFormat进行格式化

date:调用DateFormat进行格式化

time:调用DateFormat进行格式化

choice:调用ChoiceFormat进行格式化


  • FormatStyle::设置使用的格式化样式

short

medium

long

full

integer

currency

percent

SubformatPattern (子格式模式,形如#.##)


对于具体的使用方法就不多赘述了,大家可以自行百度。


资源文件的加载

在实现国际化的过程中,由于我们的用户界面信息、报错信息等内容都不能采用硬编码的方式,所以为了在不同的区域/语言环境下能进行不同的显示,我们需要为不同的环境提供不同的资源文件,同时需要遵循一定的规范。


  • 命名规范:资源名_语言代码_国/地区代码.properties

举一个例子:假设资源名为content,语言为英文,国家为美国,则与其对应的本地化资源文件命名为content_en_US.properties。


下面以IDEA为例,创建资源文件并加载读取


1.创建资源文件,在Resource目录下,创建一个Bundle

微信图片_20221112192419.png

2.添加需要兼容的区域/语言,我这里就添加一个英语/美国,给这个Bundle命名为i18n,名字随意

微信图片_20221112192502.png

3.此时会在Resource目录下生成如下的目录结构

微信图片_20221112192530.png

在两个配置文件中,我分别添加了两段配置:

【i18n.properties】:

#小明(资源文件对文件内容有严格的要求:只能包含ASCII字符,所以必须将非ASCII字符的内容转换为Unicode代码的表示方式)
name=\u5c0f\u660e
#他十九岁了
age=19

【i18n_en_US.properties】:

name=Xiaoming
age=19

示例代码:

public static void main(String[] args) {
    // i18n要跟我们之前创建的Bundle的名称一致
    // Locale.US指定了我们要拿这个Bundle下的哪个区域/语言对于的资源文件,这里获取到的是i18n_en_US.properties这个配置文件
    ResourceBundle usResourceBundle = ResourceBundle.getBundle("i18n", Locale.US);
    System.out.println(usResourceBundle.getString("name")); // 输出Xiaoming
    System.out.println(usResourceBundle.getString("age"));
    ResourceBundle chinaResourceBundle = ResourceBundle.getBundle("i18n");
    System.out.println(chinaResourceBundle.getString("name"));  // 输出小明
    System.out.println(chinaResourceBundle.getString("age"));
}

Spring中的MessageSource


在聊完了JAVA中的国际化后,我们回归主题,ApplicationContext接口继承了MessageSource接口,MessageSource接口又提供了国际化的功能,所以ApplicationContext也具有国际化的功能。接下来我们着重看看MessageSource这个接口。


接口定义

public interface MessageSource {
     //code表示国际化资源中的属性名;args用于传递格式化串占位符所用的运行期参数;
     //当在资源找不到对应属性名时,返回defaultMessage参数所指定的默认信息;
     //locale表示本地化对象;
    String getMessage(String code, Object[] args, String defaultMessage, Locale locale);
    //与上面的方法类似,只不过在找不到资源中对应的属性名时,
    //直接抛出NoSuchMessageException异常;
    String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;
    //将属性名、参数数组以及默认信息封装起来,它的功能和第一个接口方法相同。
    String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
}

UML类图微信图片_20221112192848.png

1.我们依次分析下各个类的作用

HierarchicalMessageSource,该接口提供了设置获取父容器的方法,用于构建MessageSource体系的父子层级结构。其方法定义如下:

// 为当前MessageSource设置父MessageSource
void setParentMessageSource(@Nullable MessageSource parent);
// 获取当前MessageSource的父MessageSource
@Nullable
MessageSource getParentMessageSource();
  1. MessageSourceSupport,这个类的作用类似于我们之前介绍的MessageFormat,主要提供了对消息的格式化功能。从这个继承关系中我们也能看出,Spring在设计时将消息的获取以及格式化进行了分隔。而在我们实际使用到具体的实现类时,又将功能做了聚合。
  2. DelegatingMessageSource,将所有获取消息的请求委托给父类查找,如果父类没有就报错
  3. AbstractMessageSource,实现了HierarchicalMessageSource,提供了对消息的通用处理方式,方便子类对具体的消息类型实现特定的策略
  4. AbstractResourceBasedMessageSource,提供了对Bundle的处理方式
  5. ResourceBundleMessageSource,基于JDK的ResourceBundle实现,可以根据名称加载Bundle
  6. ReloadableResourceBundleMessageSource,提供了定时刷新功能,允许在不重启系统的情况下,更新资源的信息。
  7. StaticMessageSource,主要用于程序测试


Spring中的简单使用

我这里直接取官网中的Demo,先看官网上的一段说明:

微信图片_20221112193035.png

从上文中,我们可以得出以下几点信息:


  1. Spring容器在启动时会自动查找一个名称定义的messageSource的Bean(同时需要实现MessageSource接口),如果找到了,那么所有获取信息的请求都会由这个类处理。如果在当前容器中没有找到的话,会在父容器中继续查找。
  2. 如果没有找到,那么Spring会自己new一个DelegatingMessageSource对象,并用这个对象处理消息

基于上面的结论,我们可以做如下配置:

<!--application.xml-->
<bean id="messageSource"
      class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basenames">
        <list>
            <value>format</value>
            <value>exceptions</value>
            <value>windows</value>
        </list>
    </property>
</bean>

同时配置下面三个properties文件:

# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.

测试代码:

public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("application.xml");
    String message1 = resources.getMessage("message", null, "Default", null);
    String message2 = resources.getMessage("argument.required",
                                           new Object [] {"userDao"}, "Required", null);
    System.out.println(message1); // 输出 Alligators rock!
    System.out.println(message2); // 输出 The userDao argument is required.
}

同时Spring对资源的加载也遵循我们在JAVA国际化中提到的规范,我们可以将上面例子中的exceptions.properties,更名为exceptions_en_GB.properties。

// 可以看出这种方式跟我们使用JAVA直接加载国际化资源没有太大差别
public static void main(final String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("argument.required",
        new Object [] {"userDao"}, "Required", Locale.UK);
    System.out.println(message);
}
相关文章
|
5月前
|
Java 应用服务中间件 Spring
Spring5源码(50)-SpringMVC源码阅读环境搭建
Spring5源码(50)-SpringMVC源码阅读环境搭建
74 0
|
4月前
|
存储 Java 程序员
Spring 注册BeanPostProcessor 源码阅读
Spring 注册BeanPostProcessor 源码阅读
|
5月前
|
Java 测试技术 数据库连接
【Spring源码解读!底层原理高级进阶】【下】探寻Spring内部:BeanFactory和ApplicationContext实现原理揭秘✨
【Spring源码解读!底层原理高级进阶】【下】探寻Spring内部:BeanFactory和ApplicationContext实现原理揭秘✨
|
4月前
|
存储 Java C++
理解SpringIOC和DI第一课(Spring的特点),IOC对应五大注解,ApplicationContext vs BeanFactory
理解SpringIOC和DI第一课(Spring的特点),IOC对应五大注解,ApplicationContext vs BeanFactory
|
5月前
|
Java Spring 容器
Spring底层原理之 BeanFactory 与 ApplicationContext
Spring底层原理之 BeanFactory 与 ApplicationContext
59 3
|
5月前
|
XML Java 开发者
【Spring源码解读 底层原理高级进阶】【上】探寻Spring内部:BeanFactory和ApplicationContext实现原理讲解
【Spring源码解读 底层原理高级进阶】【上】探寻Spring内部:BeanFactory和ApplicationContext实现原理讲解
|
11月前
|
Java Spring
Spring源码系列(补充):详解ApplicationContext
本文介绍了Spring框架中ApplicationContext的几个核心概念,包括MessageSource、ResourcePatternResolver、获取运行时环境、ApplicationEventPublisher和OrderComparator。对于每个概念,文章都提供了具体的用法示例和注意事项。如果您正在学习或使用Spring框架,本文将为您提供有价值的参考。
Spring源码系列(补充):详解ApplicationContext
|
5月前
|
XML Java 数据格式
Spring5源码(26)-ApplicationContext容器refresh过程简析
Spring5源码(26)-ApplicationContext容器refresh过程简析
56 0
|
5月前
|
Java C++ Spring
深入Spring原理-1.BeanFactory与ApplicationContext的区别
深入Spring原理-1.BeanFactory与ApplicationContext的区别
89 0
|
10月前
|
XML Java 数据格式
[读书笔记]Spring中BeanFactory和ApplicationContext的联系和区别
[读书笔记]Spring中BeanFactory和ApplicationContext的联系和区别
50 0