美文网首页Spring
Spring IoC容器之神通广大的ApplicationCon

Spring IoC容器之神通广大的ApplicationCon

作者: 现实目标决心 | 来源:发表于2018-07-24 23:16 被阅读29次

    上一篇讲了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的方式注入如下:

    <bean id="messageSource" class="org.springframework.context.support. ➥ ResourceBundleMessageSource"> 
      <property name="basenames">
      <list>
      <value>messages</value> <value>errorcodes</value> 
      </list>
    </property>
    </bean>
    
    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<ApplicationReadyEvent> {
        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容器之神通广大的ApplicationCon

        本文链接:https://www.haomeiwen.com/subject/hqqumftx.html