先看一下ApplicationContext的类结构:
image.png

可知:ApplicationContext是BeanFactory,所以具有BeanFactory的能力:初始化Spring容器(细节下一篇文章分析),除此之外,ApplicaitonContext还具有如下能力:

  1. MessageSource接口提供的i18n能力。
  2. ResourceLoader接口提供的资源文件访问能力。
  3. ApplicationListener接口提供的事件发布及监听能力。
  4. HierarchicalBeanFactory提供的能力。

今天主要关注前3项。

i18n能力

即使不是国际化项目,i18n也很实用,比如项目中的提示信息或错误信息的管理,实在是见过太多项目对错误信息的放任不管,其实提示信息或者错误信息是系统与用户交互的重要组成部分,有必要认真对待:i18n就是一个选择。

ApplicationContext通过MessageSource接口提供了i18n能力,简单易用。

Spring提供了MessageSource的3个实现:ResourceBundleMessageSource, ReloadableResourceBundleMessageSource和 StaticMessageSource。我们就以ResourceBundleMessageSource举例。

我们知道使用i18n需要以下步骤:

  1. 创建property文件,定义message
  2. 创建MessageSource加载propperty文件
  3. 通过MessageSource获取property文件定义的message

下面我们以xml和注解两种方式举例。

1. 创建property文件

创建property文件是通用的,两种方式无区别,所以我们首先创建property文件。

为了支持中文,我们需要把项目的文件编码方式设置为UTF-8、property文件的编码方式选择为UTF-8,并且一定勾选native-to-accii这一选项,否则会出现中文乱码:
image.png

然后在resources目录下创建一个myMessage的、包含en和zh两个语言resource Bundle文件,创建完成后如下图:

image.png
打开myMessage_en.propperties文件,输入:

msg=this is {0}

打开myMessage_zh.properties文件输入:

msg=我其实就是要汉语的提示信息 {0}

这个时候到classpath下看一下myMessage_zh.properties:

msg=\u7EC8\u4E8E\u53EF\u4EE5\u6C49\u5B57\u4E86\u554A {0}

可以看到是转换为accii码以后的文件了,这个就是native-to-ascii选项的作用,如果这里看到的依然是汉字,最终出来的大概率是乱码。

好了,properties文件创建好了。

xml方式实现i18n

还是用我们以前的例子,在mySpring.xml文件中加入如下配置信息,把ResourceBundleMessageSource类加载到Spring容器中并指定配置文件名称为myMessage:

  <bean id="messageSource"
          class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>myMessage</value>
            </list>
        </property>
    </bean>

然后在xml启动类中加入对message的调用代码:

public class AppByXML {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("mySpring.xml");
        String msg = applicationContext.getMessage("msg",new Object[]{"汉字"},"default msg", Locale.CHINESE);
        System.out.println("msg:"+msg);

    }
}

执行启动类:

msg:我其实就是要汉语的提示信息 汉字

然后修改一下启动类,让他调用en配置文件:

public class AppByXML {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("mySpring.xml");
        String msg = applicationContext.getMessage("msg",new Object[]{"english"},"default msg", Locale.ENGLISH);
        System.out.println("msg:"+msg);

    }

执行启动类:

msg:this is english

xml配置方式下调用applicationContext的i18n功能成功!

注解方式

只要把MessageSource注入到Spring容器中就可以,所以,在MyConfiguration文件中:

@Configuration()
@ComponentScan(value={"springTest"})
public class MyConfiguration {
    @Bean
    public MessageSource messageSource(){
        ResourceBundleMessageSource rbm = new ResourceBundleMessageSource();
        rbm.setBasenames("myMessage");
        return rbm;
    }
}

启动类:

public class App {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfiguration.class);
//        (ResourceBundleMessageSource)applicationContext

        String msg = applicationContext.getMessage("msg",new Object[]{"test"},"default msg", Locale.ENGLISH);

        System.out.println("msg:"+msg);
}

执行启动类就OK。

事件监听

Spring的事件监听机制遵循订阅者模式,整个事件监听框架由以下3部分组成:

  1. 事件:ApplicationEvent接口实现
  2. 事件发布:由ApplicationEventPublisher接口完成。
  3. 事件监听:ApplicationListener接口负责。

由于ApplicationContext实现了ApplicationEventPublisher接口,所以他天生就是事件发布器。

Spring4.2之后,加强了对事件监听机制的支持,可以支持通过注解发布和监听事件,而且不仅支持发布ApplicationEvent事件,还可以支持普通对象(不需要实现ApplicationEvent接口)的事件发布。

发布未实现ApplicationEvent事件后,ApplicationContext会自动封装其为PayloadApplicationEvent:

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
        Assert.notNull(event, "Event must not be null");

        // Decorate event as an ApplicationEvent if necessary
        ApplicationEvent applicationEvent;
        if (event instanceof ApplicationEvent) {
            applicationEvent = (ApplicationEvent) event;
        }
        else {
            applicationEvent = new PayloadApplicationEvent<>(this, event);
            if (eventType == null) {
                eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
            }
        }

原始对象被封装在PayloadApplicationEvent的payload属性中,在监听器中可以通过payload获取。

允许的情况下,还是建议发布ApplicationEvent事件,这样的话可以实现有针对性的监听器、专门监听相应事件,这种情况下监听器的onApplicationEvent方法中的事件就是类型安全的、不需要检查和强制向下转换。

监听器必须要作为bean注入到Spring容器中,否则的话是监听不到ApplicationContext发布的事件的。

Spring的监听器可以有以下两种实现方式:

  1. 实现ApplicationListener接口
  2. 监听方法上使用@EventListener注解

应用层发布事件是需要ApplicationContext对象的,所以,在应用层需要获取到ApplicationContext对象,以什么样的方式获取比较方便呢?

自然而然,回想以下前面学习过的内容Spring FrameWork从入门到NB - 定制Bean,其中提到ApplicationContextAware接口:“ApplicationContextAware接口提供了一个机会,让你从普通的Spring Bean中可以获得到创建他的ApplicationContext。”这就够了!

异步监听器

需要特别注意的是,默认情况下,事件监听器的处理逻辑和事件发布的主逻辑是处于同一个线程中同步执行的。好处是事件监听逻辑可以和事件发布逻辑共享事务(如果存在事务、而且业务有要求的话)。

但如果我们有特殊需求,想要异步事件监听器,该怎么实现呢?

Spring那么NB,实现方式当然非常简单:在Listner上加@Async注解就可以。注意需要在配置类上增加@EnableAsync注解,否则@Async不会生效。

监听事件排序

依然是要在监听器上做文章,在监听器ApplicationListener上、或者@EnentLister注解方法上使用@Order注解。
上一篇 Spring FrameWork从入门到NB - Environment Abstraction
下一篇 Spring FrameWork从入门到NB - Spring AOP - 概念


45 声望17 粉丝