美文网首页
ApplicationContext Spring揭秘阅读总结

ApplicationContext Spring揭秘阅读总结

作者: 好好学习Sun | 来源:发表于2018-09-08 17:51 被阅读28次

            作为Spring提供的较之BeanFactory更为先进的IoC容器实现,ApplicationContext除了拥有BeanFactory支持的所有功能之外,还进一步扩展了基本容器的功能,包括BeanFactoryPostProcessor、BeanPostProcessor以及其他特殊类型bean的自动识别、容器启动后bean实例的自动初始化、国际化的信息支持、容器内事件发布等。真是“青出于蓝而胜于蓝”啊!

            Spring为基本的BeanFactory类型容器提供了XmlBeanFactory实现。相应地,它也为ApplicationContext类型容器提供了以下几个常用的实现。

     org.springframework.context.support.FileSystemXmlApplicationContext。在默认情况下,从文件系统加载bean定义以及相关资源的ApplicationContext实现。

     org.springframework.context.support.ClassPathXmlApplicationContext。在默认情况下,从Classpath加载bean定义以及相关资源的ApplicationContext实现。

     org.springframework.web.context.support.XmlWebApplicationContext。Spring提供的用于Web应用程序的ApplicationContext实现,我们将在第六部分更多地接触到它。

            更多实现可以参照org.springframework.context.ApplicationContext接口定义的Javadoc,这里不再赘述。

            第4章中说明了ApplicationContext所支持的大部分功能。下面主要围绕ApplicationContext较之BeanFactory特有的一些特性展开讨论,即国际化(I18n)信息支持、统一资源加载策略以及容器内事件发布等。

    5.1 统一资源加载策略

            要搞清楚Spring为什么提供这么一个功能,还是从Java SE提供的标准类java.net.URL说起比较好。URL全名是Uniform Resource Locator(统一资源定位器),但多少有些名不副实的味道。

            首先,说是统一资源定位,但基本实现却只限于网络形式发布的资源的查找和定位工作,基本上只提供了基于HTTP、FTP、File等协议(sun.net.www.protocol包下所支持的协议)的资源定位功能。虽然也提供了扩展的接口,但从一开始,其自身的“定位”就已经趋于狭隘了。实际上,资源这个词的范围比较广义,资源可以任何形式存在,如以二进制对象形式存在、以字节流形式存在、以文件形式存在等;而且,资源也可以存在于任何场所,如存在于文件系统、存在于Java应用的Classpath中,甚至存在于URL可以定位的地方。

            其次,从某些程度上来说,该类的功能职责划分不清,资源的查找和资源的表示没有一个清晰的界限。当前情况是,资源查找后返回的形式多种多样,没有一个统一的抽象。理想情况下,资源查找完成后,返回给客户端的应该是一个统一的资源抽象接口,客户端要对资源进行什么样的处理,应该由资源抽象接口来界定,而不应该成为资源的定位者和查找者同时要关心的事情。

            所以,在这个前提下,Spring提出了一套基于org.springframework.core.io.Resource和org.springframework.core.io.ResourceLoader接口的资源抽象和加载策略。

    5.1.1 Spring中的Resource

            Spring框架内部使用org.springframework.core.io.Resource接口作为所有资源的抽象和访 问接口,我们之前在构造BeanFactory的时候已经接触过它,如下代码:

            BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("...")); 

            其中ClassPathResource就是Resource的一个特定类型的实现,代表的是位于Classpath中的资源。

            Resource接口可以根据资源的不同类型,或者资源所处的不同场合,给出相应的具体实现。Spring框架在这个理念的基础上,提供了一些实现类(可以在org.springframework.core.io包下找到这些实现类)。

     ByteArrayResource。将字节(byte)数组提供的数据作为一种资源进行封装,如果通过InputStream形式访问该类型的资源,该实现会根据字节数组的数据,构造相应的ByteArray-InputStream并返回。

     ClassPathResource。该实现从Java应用程序的ClassPath中加载具体资源并进行封装,可以使用指定的类加载器(ClassLoader)或者给定的类进行资源加载。9

     FileSystemResource。对java.io.File类型的封装,所以,我们可以以文件或者URL的形式对该类型资源进行访问,只要能跟File打的交道,基本上跟FileSystemResource也可以。10

     UrlResource。通过java.net.URL进行的具体资源查找定位的实现类,内部委派URL进行具体的资源操作。11  InputStreamResource。将给定的InputStream视为一种资源的Resource实现类,较为少用。可能的情况下,以ByteArrayResource以及其他形式资源实现代之。

            如果以上这些资源实现还不能满足要求,那么我们还可以根据相应场景给出自己的实现,只需实现org.springframework.core.io.Resource接口就是了。

    5.1.2 ResourceLoader,“更广义的URL”

            资源是有了,但如何去查找和定位这些资源,则应该是ResourceLoader的职责所在了。org.springframework.core.io.ResourceLoader接口是资源查找定位策略的统一抽象,具体的资源查找定位策略则由相应的ResourceLoader实现类给出。我想,把ResourceLoader称作统一资源定位器或许才更恰当一些吧!ResourceLoader定义如下:

            其中最主要的就是Resource getResource(String location);方法,通过它,我们就可以根据指定的资源位置,定位到具体的资源实例。

    1. 可用的ResourceLoader

     DefaultResourceLoader

            ResourceLoader有一个默认的实现类,即org.springframework.core.io.DefaultResource-Loader,该类默认的资源查找处理逻辑如下。

    (1) 首先检查资源路径是否以classpath:前缀打头,如果是,则尝试构造ClassPathResource类型资源并返回。

    (2) 否则,(a) 尝试通过URL,根据资源路径来定位资源,如果没有抛出MalformedURLException,有则会构造UrlResource类型的资源并返回;(b)如果还是无法根据资源路径定位指定的资源,则委派getResourceByPath(String) 方法来定位, DefaultResourceLoader 的getResourceByPath(String)方法默认实现逻辑是,构造ClassPathResource类型的资源并返回。

     FileSystemResourceLoader

            为了避免DefaultResourceLoader在最后getResourceByPath(String)方法上的不恰当处理,我们可以使用org.springframework.core.io.FileSystemResourceLoader,它继承自Default-ResourceLoader,但覆写了getResourceByPath(String)方法,使之从文件系统加载资源并以FileSystemResource类型返回。这样,我们就可以取得预想的资源类型。代码清单5-3中的代码将帮助我们验证这一点。

            FileSystemResourceLoader在ResourceLoader家族中的兄弟FileSystemXmlApplication-Context,也是覆写了getResourceByPath(String)方法的逻辑,以改变DefaultResourceLoader的默认资源加载行为,最终从文件系统中加载并返回FileSystemResource类型的资源。

    2. ResourcePatternResolver——批量查找的ResourceLoader

            ResourcePatternResolver是ResourceLoader的扩展,ResourceLoader每次只能根据资源路径返回确定的单个Resource实例,而ResourcePatternResolver则可以根据指定的资源路径匹配模式,每次返回多个Resource实例。接口org.springframework.core.io.support.ResourcePattern-Resolver定义如下:

    3. 回顾与展望

            现在我们应该对Spring的统一资源加载策略有了一个整体上的认识,就如图5-1所示。

    5.1.3 ApplicationContext与ResourceLoader

            ApplicationContext继承了ResourcePatternResolver,当 然就间接实现了ResourceLoader接口。所以,任何的ApplicationContext实现都可以看作是一个ResourceLoader甚至ResourcePatternResolver。而这就是ApplicationContext支持Spring内统一资源加载策略的真相。

    1. 扮演ResourceLoader的角色

            既然ApplicationContext可以作为ResourceLoader或者ResourcePatternResolver来使用,那么,很显然,我们可以通过ApplicationContext来加载任何Spring支持的Resource类型。与直接使用ResourceLoader来做这些事情相比,很明显,ApplicationContext的表现过于“谦虚”了。

    2. ResourceLoader类型的注入

            在大部分情况下,如果某个bean需要依赖于ResourceLoader来查找定位资源,我们可以为其注入容器中声明的某个具体的ResourceLoader实现,该bean也无需实现任何接口,直接通过构造方法注入或者setter方法注入规则声明依赖即可,这样处理是比较合理的。不过,如果你不介意你的bean定义依赖于Spring的API,那不妨考虑用一下Spring提供的便利。

    3. Resource类型的注入

            我们之前讲过,容器可以将bean定义文件中的字符串形式表达的信息,正确地转换成具体对象定义的依赖类型。对于那些Spring容器提供的默认的PropertyEditors无法识别的对象类型,我们可以提供自定义的PropertyEditor实现并注册到容器中,以供容器做类型转换的时候使用。

    5.2.1 Java SE提供的国际化支持

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

    1. Locale

            不同的Locale代表不同的国家和地区,每个国家和地区在Locale这里都有相应的简写代码表示, 包括语言代码以及国家代码,这些代码是ISO标准代码。如,Locale.CHINA代表中国,它的代码表示为zh_CN;Locale.US代表美国地区,代码表示为en_US;而美国和英国等都属于英语地区,则可以使用Locale.ENGLISH来统一表示,这时代码只有语言代码,即en。

    2. ResourceBundle

            ResourceBundle用来保存特定于某个Locale的信息(可以是String类型信息,也可以是任何类型的对象)。通常,ResourceBundle管理一组信息序列,所有的信息序列有统一的一个basename,然后特定的Locale的信息,可以根据basename后追加的语言或者地区代码来区分。

    5.2.2 MessageSource与ApplicationContext

            Spring在Java SE的国际化支持的基础上,进一步抽象了国际化信息的访问接口,也就是org.springframework.context.MessageSource。通过该接口,我们统一了国际化信息的访问方式。传入相应的Locale、资源的键以及相应参数,就可以取得相应的信息,再也不用先根据Locale取得ResourceBundle,然后再从ResourceBundle查询信息了。

    5.3 容器内部事件发布

            Spring的ApplicationContext容器提供的容器内事件发布功能,是通过提供一套基于Java SE标准自定义事件类而实现的。为了更好地了解这组自定义事件类,我们可以先从Java SE的标准自定义事件类实现的推荐流程说起。

    5.3.1 自定义事件发布

            Java SE提供了实现自定义事件发布(Custom Event publication)功能的基础类,即java.util.EventObject类和java.util.EventListener接口。所有的自定义事件类型可以通过扩展EventObject来实现,而事件的监听器则扩展自EventListener。下面让我们看一下要实现一套自定义事件发布类的架构,应该如何来做。

            给出自定义事件类型(define your own event object)。为了针对具体场景可以区分具体的事件类型,我们需要给出自己的事件类型的定义,通常做法是扩展java.util.EventObject类来实现自定义的事件类型。

    5.3.2 Spring 的容器内事件发布类结构分析

            Spring 的ApplicationContext 容器内部允许以org.springframework.context.ApplicationEvent的形式发布事件, 容器内注册的org.springframework.context.Application-Listener类型的bean定义会被ApplicationContext容器自动识别,它们负责监听容器内发布的所有ApplicationEvent类型的事件。也就是说,一旦容器内发布ApplicationEvent及其子类型的事件,注册到容器的ApplicationListener就会对这些事件进行处理。

    5.3.3 Spring 容器内事件发布的应用

            Spring的ApplicationContext容器内的事件发布机制,主要用于单一容器内的简单消息通知和处理,并不适合分布式、多进程、多容器之间的事件通知。虽然可以通过Spring的Remoting支持,“曲折一点”来实现较为复杂的需求,但是难免弊大于利,失大于得。其他消息机制处理较复杂场景或许更合适。所以,我们应该在合适的地点、合适的需求分析的前提下,合理地使用Spring提供的ApplicationContext容器内的事件发布机制。

            要让我们的业务类支持容器内的事件发布,需要它拥有ApplicationEventPublisher的事件发 布支持。所以,需要为其注入ApplicationEventPublisher实例。可以通过如下两种方式为我们的业务对象注入ApplicationEventPublisher的依赖。

     使用ApplicationEventPublisherAware接口。在ApplicationContext类型的容器启动时,会自动识别该类型的bean定义并将ApplicationContext容器本身作为ApplicationEvent-Publisher注入当前对象,而ApplicationContext容器本身就是一个ApplicationEvent-Publisher。

     使用ApplicationContextAware接口。既然ApplicationContext本身就是一个Application-EventPublisher,那么通过ApplicationContextAware几乎达到第一种方式相同的效果。


    相关文章

      网友评论

          本文标题:ApplicationContext Spring揭秘阅读总结

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