Spring IoC容器之神通广大的ApplicationContext

上一篇讲了Spring IoC容器服务提供者BeanFactory。这一篇主要来研究下ApplicationContext。

在开始之前,我们需要知道Spring IoC容器和IoC Service Provider之间的关系:IoC Service Provider是Spring IoC容器体系的一部分。


Spring的IoC容器和IoC Service Provider之间的关系

在spring家族中,承诺提供这项IoC Service Provider服务的主要有以下这两个接口:

  • BeanFactory
    基础类型IoC容器,提供完整的IoC服务支持。默认采用延迟加载,也就是在需要的时候,才去召唤、创建对象。故在项目启动的时候可以非常快。缺点是在使用时,第一次召唤对象会比较慢。
  • ApplicationContext
    ApplicationContext在BeanFactory的基础上构建,是相对比较高
    级的容器实现,除了拥有BeanFactory的所有支持,ApplicationContext还提供了其他高级特性。ApplicationContext所管理的对象,在该类型容器启动之后,默认全部初始化并绑定完成。启动时需要大量资源,但在真正使用的时候,可以迅速将对象召唤出来。

BeanFactory和ApplicationContext继承关系如下:


BeanFactory和ApplicationContext继承关系

从上面的继承图,可以看出ApplicationContext支持的功能:

对象的创建管理(BeanFactory),上篇文章已讲,本篇不重复。
统一资源加载策略(ResourceLoader)
国际化信息支持(MessageSource)
容器内部事件发布(ApplicationEventPublisher)
多配置模块加载的简化

ApplicationContext只是一个接口,声明了一些功能,但并没有指定具体的实现。ApplicationContext常用的实现类有以下几个:

  • FileSystemXmlApplicationContext
    从文件系统加载bean定义以及相关资源的ApplicationContext实现
  • ClassPathXmlApplicationContext
    从Classpath加载bean定义以及相关资源的ApplicationContext实现
  • XmlWebApplicationContext
    Spring提供的用于Web应用程序的ApplicationContext实现

0x01 统一资源加载策略

这里有两个关键名词:资源和加载策略。
资源用Resource接口去抽象,加载策略用ResourceLoader接口去抽象。

资源Resource

资源Resource最终体现为一个文件对象File,以及一些关于文件的描述,如是否关闭,是否存在,文件名,路径等。Resource接口定义如下:

public interface Resource extends InputStreamSource {
boolean exists();
boolean isOpen();
URL getURL() throws IOException;
File getFile() throws IOException; 
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}

根据不同的场景场合,资源的来源也不一样,故对资源接口Resource有不同的实现类。

FileSystemResource:以文件或者URL的形式对该类型资源进行访问.
ClassPathResource:从ClassPath中加载具体资源并进行封装.
UrlResource:通过java.net.URL进行的具体资源查找定位的实现类.
ByteArrayResource。将字节(byte)数组提供的数据作为一种资源进行封装

加载策略ResourceLoader

资源是有了,但如何去查找和定位这些资源,则应该是ResourceLoader的职责所在了。org.springframework.core.io.ResourceLoader接口是资源查找定位策略的统一抽象,具体的资源查找定位策略则由相应的ResourceLoader实现类给出。

资源加载策略的接口定义如下:

public interface ResourceLoader {
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
Resource getResource(String location);
ClassLoader getClassLoader();
}

对不同的资源,也应该有不同的加载策略。已经实现的加载策略如下:

DefaultResourceLoader:支持路径以classpath:和URL前缀打头的资源加载。
FileSystemResourceLoader:从文件系统中加载具体资源并进行封装.
ResourcePatternResolver:批量查找的ResourceLoader

常用的是ResourcePatternResolver资源加载器。因为可以扫描某个指定包下面的所有资源。接口定义如下:

public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}

ResourcePatternResolver在继承ResourceLoader原有定义的基础上,又引入了Resource[] getResources(String)方法定义,以支持根据路径匹配模式返回多个Resources的功能。ResourcePatternResolver最常用的一个实现是org.springframework.core.io.support.PathMatchingResourcePatternResolver。

现在我们应该对Spring的统一资源加载策略有了一个整体上的认识。

Resource和ResourceLoader类层次图

对于以上图,简单来说就是三步:

1 首先对资源进行定义,于是就有了Resource接口。
2 然后,想办法如何将资源加载进来,有了ResourceLoader接口以及各种实现。
3 如何加载多个资源,有了PathMatchingResourcePatternResolver实现

讲了资源和加载策略,我们再来看看ApplicationContext是如何将它们给整合进来的。


ApplicationContext和资源加载策略的关系

继承DefaultResourceLoader去加载资源,组合PathMatchingResourcePatternResolver去实现多个资源的获取。这就是ApplicationContext的统一资源加载策略。

0x02 国际化支持MessageSource

对于Java中的国际化信息处理,主要涉及两个类,即java.util.Locale和java.util.ResourceBundle。

1. Locale

不同的Locale代表不同的国家和地区每个国家和地区在Locale这里都有相应的简写代码表示, 包括语言代码以及国家代码,这些代码是ISO标准代码。如,Locale.CHINA代表中国,它的代码表示
为zh_CN;Locale.US代表美国地区,代码表示为en_US;

Locale(String language)
Locale(String language, String country)
L ocale(String language, String country, String variant)

2. ResourceBundle

ResourceBundle用来保存特定于某个Locale的信息。ResourceBundle管理一组信息序列,所有的信息序列有统一的一个basename,如下:

messages.properties
messages_zh.properties
messages_zh_CN.properties
messages_en.properties
messages_en_US.properties

其中,文件名中的messages部分称作ResourceBundle将加载的资源的basename,其他语言或地区的资源在basename的基础上追加Locale特定代码。

有了ResourceBundle对应的资源文件之后,我们就可以通过ResourceBundle的

getBundle(String baseName, Locale locale)

方法取得不同Locale对应的ResourceBundle,然后根据资源
的键取得相应Locale的资源条目内容。

Spring在Java SE的国际化支持的基础上,进一步抽象了国际化信息的访问接口,也就是
org.springframework.context.MessageSource,该接口定义如下:

public interface MessageSource {
String getMessage(String code, Object[] args, String defaultMessage, Locale locale);
String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws ➥
NoSuchMessage Exception;
}

以上的对国际化的访问也只是接口,要有具体的实现才能满足我们的需求。

Spring提供了三种MessageSource的实现,即StaticMessageSource、ResourceBundleMessage-
Source和ReloadableResourceBundleMessageSource。
最常用的就是ResourceBundleMessageSource
使用xml的方式注入如下:

 
  
  
  messages errorcodes 
  


MessageSource类层次结构

国际化的使用场景如下:

1 启动的时候,加载默认的国际化文件。
2 web访问的时候,传入Locale对象。

常用的,当然是第二种场景。
代码如下:

//获取当前请求线程的RequestContext 
private static RequestContext getRequestContext() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();
        return new RequestContext(request);
    }
//获取本次请求的Locale
public static Locale getLocale() {
        Locale locale = curLocale.get();
        // 取得界面的Locale
        return locale == null ? getRequestContext().getLocale() : locale;
    }

0x03 事件的发布

事件发布三要素:

1 要有事件 EventObject
2 要有监听者 EventListener
3 要有发布者 EventPublisher

一句话整合这三要素:发布者遍历内部的监听者列表,然后调用各个监听者对事件的处理接口对事件进行处理。

事件三要素类结构

再回顾一个ApplicationContext的类继承树,这次我们要讲解的是ApplicationEventPublisher类。

BeanFactory和ApplicationContext继承关系

下面是Spring容器内部事件发布的实现类图:


Spring容器内事件发布实现类图

不难看出,ApplicationContext容器现在担当的就是事件发布者的角色。但ApplicationContext毕竟都只是接口,总要有具体的类去干活才行,实际的工作是另有其人去完成,这个事件发布的工作由AbstractApplicationContext委托给ApplicationEventMulticaster接口体系去完成(这里使用了策略模式)。但整体的体系结构就是上面说的事件发布三要素。

Spring容器在启动的时候,会在各个关键节点发布消息。这些事件有如下几种:

ContextClosedEvent:ApplicationContext容器在即将关闭的时候发布的事件类型。
ContextRefreshedEvent:ApplicationContext容器在初始化或者刷新的时候发布的事件类型。
RequestHandledEvent:Web请求处理后发布的事件,其有一子类ServletRequestHandledEvent提供特定于Java EE的Servlet相关事件。
ApplicationReadyEvent:容器准备好后,发布的事件

如果你对这些消息感兴趣,那么你就将这个事件的监听器注册进来。

实现listener接口,直接使用@Component注解即可。

例如,在容器启动准备完成后,打印启动成功:

@Component
public class ServerApplicationListener implements ApplicationListener {
    private Logger logger = LoggerFactory.getLogger(ServerApplicationListener.class);

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        logger.info("START APPLICATION SUCCESS ");
    }
}

以上是监听Spring容器自己发布的事件。如果你想要使用Spring容器的事件处理机制,在自己认为合适的地点,发布自定义的事件,然后来监听改事件,应该怎么编写代码?
只需以下四个步骤:

1 继承EventObject定义自己的事件
2 实现ApplicationListener,执行事件处理逻辑
3 实现ApplicationEventPublisherAware接口,获取ApplicationEventPublisher事件发布类,发布事件。
4 将自定义的事件处理器和事件发布类交给Spring容器托管

总结:

ApplicationContext是Spring在BeanFactory基础容器之上,提供的另一个IoC容器实现。它拥有许多BeanFactory所没有的特性,包括统一的资源加载策略、国际化信息支持、容器内事件发布以及简化的多配置文件加载功能。本章对ApplicationContext的这些新增特性进行了详尽的阐述。希望读者在学习完本章内容之后,对每一种特性的来龙去脉都能了如指掌。

你可能感兴趣的:(Spring IoC容器之神通广大的ApplicationContext)