Spring 资源访问剖析和策略模式应用(转)
?
Spring 把所有能记录信息的载体,如各种类型的文件、二进制流等都称为资源,对 Spring 开发者来说,最常用的资源就是 Spring 配置文件(通常是一份 XML 格式的文件)。
在 Sun 所提供的标准 API 里,资源访问通常由 java.net.URL 和文件 IO 来完成,尤其是当我们需要访问来自网络的资源时,通常会选择 URL 类。
URL 类可以处理一些常规的资源访问问题,但依然不能很好地满足所有底层资源访问的需要,比如,暂时还无法从类加载路径、或相对于 ServletContext 的路径来访问资源,虽然 Java 允许使用特定的 URL 前缀注册新的处理类(例如已有的 http: 前缀的处理类),但是这样做通常比较复杂,而且 URL 接口还缺少一些有用的功能,比如检查所指向的资源是否存在等。
Spring 改进了 Java 资源访问的策略。Spring 为资源访问提供了一个 Resource 接口,该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。
Resource 接口是具体资源访问策略的抽象,也是所有资源访问类所实现的接口。Resource 接口主要提供了如下几个方法:
- getInputStream():定位并打开资源,返回资源对应的输入流。每次调用都返回新的输入流。调用者必须负责关闭输入流。
- exists():返回 Resource 所指向的资源是否存在。
- isOpen():返回资源文件是否打开,如果资源文件不能多次读取,每次读取结束应该显式关闭,以防止资源泄漏。
- getDescription():返回资源的描述信息,通常用于资源处理出错时输出该信息,通常是全限定文件名或实际 URL。
- getFile:返回资源对应的 File 对象。
- getURL:返回资源对应的 URL 对象。Resource 和策略模式?Resource 接口就是策略模式的典型应用,Resource 接口就代表资源访问策略,但具体采用哪种策略实现,Resource 接口并不理会。客户端程序只和 Resource 接口耦合,并不知道底层采用何种资源访问策略,这样应用可以在不同的资源访问策略之间自由切换。
最后两个方法通常无须使用,仅在通过简单方式访问无法实现时,Resource 提供传统的资源访问的功能。
Resource 接口本身没有提供访问任何底层资源的实现逻辑,针对不同的底层资源,Spring 将会提供不同的 Resource 实现类,不同的实现类负责不同的资源访问逻辑。
Resource 不仅可在 Spring 的项目中使用,也可直接作为资源访问的工具类使用。意思是说:即使不使用 Spring 框架,也可以使用 Resource 作为工具类,用来代替 URL。当然,使用 Resource 接口会让代码与 Spring 的接口耦合在一起,但这种耦合只是部分工具集的耦合,不会造成太大的代码污染。
?
Resource 的实现类
Resource 接口是 Spring 资源访问策略的抽象,它本身并不提供任何资源访问实现,具体的资源访问由该接口的实现类完成——每个实现类代表一种资源访问策略。
Spring 为 Resource 接口提供了如下实现类:
- UrlResource:访问网络资源的实现类。
- ClassPathResource:访问类加载路径里资源的实现类。
- FileSystemResource:访问文件系统里资源的实现类。
- ServletContextResource:访问相对于 ServletContext 路径里的资源的实现类:
- InputStreamResource:访问输入流资源的实现类。
- ByteArrayResource:访问字节数组资源的实现类。
这些 Resource 实现类,针对不同的的底层资源,提供了相应的资源访问逻辑,并提供便捷的包装,以利于客户端程序的资源访问。
使用 UrlResource 访问网络资源
访问网络资源通过 UrlResource 类实现,UrlResource 是 java.net.URL 类的包装,主要用于访问之前通过 URL 类访问的资源对象。URL 资源通常应该提供标准的协议前缀。例如:file: 用于访问文件系统;http: 用于通过 HTTP 协议访问资源;ftp: 用于通过 FTP 协议访问资源等。
UrlResource 类实现 Resource 接口,对 Resource 全部方法提供了实现,完全支持 Resource 的全部 API。下面代码示范了使用 UrlResource 访问文件系统资源的示例。程序如下:
清单 1. UrlResourceTest.java?
图 1 所示的类图中提供了一个 Resouce 接口,这个接口就是 Spring 为资源访问所提供的策略接口,该接口下的大量实现类:UrlResource、ClassPathResource、FileSystemResource、ServletContextResource、ByteArrayResource、InputStreamReource 都实现了该策略接口,用于实现不同的资源访问策略。
下面我们将通过一个浅显的示例来讲解策略模式:
?
策略模式
策略模式用于封装系列的算法,这些算法通常被封装在一个被称为 Context 类中,客户端程序可以自由选择其中一种算法,或让 Context 为客户端选择一个最佳的算法——使用策略模式的优势是为了支持算法的自由切换。
考虑如下场景:现在我们正在开发一个网上书店,该书店为了更好地促销,经常需要对图书进行打折促销,程序需要考虑各种打折促销的计算方法。
为了实现书店现在所提供的各种打折需求,程序考虑使用如下方式来实现
// 一段实现 discount() 方法代码
dc.changeDiscount(new VipDiscount()); double price2 = 89; // 使用 VIP 打折得到打折价格 System.out.println("89 元的书对 VIP 用户的价格是:" + dc.getDiscountPrice(price2)); } }上面程序第一行粗体字代码创建了一个 DiscountContext 对象,客户端并未指定实际所需的打折策略类,故程序将使用默认的打折策略类;程序第二行粗体字代码指定使用 VipDiscount 策略类,故程序将改为使用使用 VIP 打折策略。
再次考虑前面的需求:当业务需要新增一种打折类型时,系统只需要新定义一个 DiscountStrategy 实现类,该实现类实现 getDiscount() 方法,用于实现新的打折算法即可。客户端程序需要切换为新的打折策略时,则需要先调用 DiscountContext 的 setDiscount() 方法切换为新的打折策略。
从上面介绍中可以看出,使用策略模式可以让客户端代码在不同的打折策略之间切换,但也有一个小小的遗憾:客户端代码需要和不同的策略类耦合。
为了弥补这个不足,我们可以考虑使用配置文件来指定 DiscountContext 使用哪种打折策略——这就彻底分离客户端代码和具体打折策略——这正好是 Spring 框架的强项,Spring 框架采用配置文件来管理 Bean,当然也可以管理资源。
下面我们再回到 Spring 框架里,看看 Spring 框架的 Context 如何“智能”地选择资源访问策略,
?
ResourceLoader 接口和 ResourceLoaderAware 接口
Spring 提供两个标志性接口:
- ResourceLoader:该接口实现类的实例可以获得一个 Resource 实例。
- ResourceLoaderAware:该接口实现类的实例将获得一个 ResourceLoader 的引用。
在 ResourceLoader 接口里有如下方法:
- Resource getResource(String location):该接口仅包含这个方法,该方法用于返回一个 Resource 实例。ApplicationContext 的实现类都实现 ResourceLoader 接口,因此 ApplicationContext 可用于直接获取 Resource 实例。策略模式的优势?当 Spring 应用需要进行资源访问时,实际上并不需要直接使用 Resource 实现类,而是调用 ApplicationContext 实例的 getResource() 方法来获得资源,ApplicationContext 将会负责选择 Resource 的实现类,也就是确定具体的资源访问策略,从而将应用程序和具体的资源访问策略分离开来,这就体现了策略模式的优势。
此处 Spring 框架的 ApplicationContext 不仅是 Spring 容器,而且它还是资源访问策略的“决策者”,也就是策略模式中 Context 对象,它将为客户端代码“智能”地选择策略实现。
当 ApplicationContext 实例获取 Resource 实例时,系统将默认采用与 ApplicationContext 相同的资源访问策略。对于如下代码:
//通过?ApplicationContext访问资源?
使用 Resource 作为属性
前面介绍了 Spring 提供的资源访问策略,但这些依赖访问策略要么需要使用 Resource 实现类,要么需要使用 ApplicationContext 来获取资源。实际上,当应用程序中的 Bean 实例需要访问资源时,Spring 有更好的解决方法:直接利用依赖注入。
从这个意义上来看,Spring 框架不仅充分利用了策略模式来简化资源访问,而且还将策略模式和 IoC 进行充分地结合,最大程度地简化了 Spring 资源访问。
归纳起来,如果 Bean 实例需要访问资源,有如下两种解决方案:
- 代码中获取 Resource 实例。
- 使用依赖注入。
对于第一种方式的资源访问,当程序获取 Resource 实例时,总需要提供 Resource 所在的位置,不管通过 FileSystemResource 创建实例,还是通过 ClassPathResource 创建实例,或者通过 ApplicationContext 的 getResource() 方法获取实例,都需要提供资源位置。这意味着:资源所在的物理位置将被耦合到代码中,如果资源位置发生改变,则必须改写程序。因此,通常建议采用第二种方法,让 Spring 为 Bean 实例依赖注入资源。
看如下 TestBean,它有一个 Resource 类型的 res Field,程序并为该 Field 提供了对应的 setter 方法,这就可以利用 Spring 的依赖注入了。
清单 9. TestBean.java?
访问 ApplicationContext 的配置文件
不管以怎样的方式创建 ApplicationContext 实例,都需要为 ApplicationContext 指定配置文件,Spring 允许使用一份或多份 XML 配置文件。
当程序创建 ApplicationContext 实例化时,通常也是以 Resource 的方式来访问配置文件的,所以 ApplicationContext 完全支持 ClassPathResource,FileSystemResource,ServletContextResouce 等资源访问方式。ApplicationContext 确定资源访问策略通常有两个方法:
- ApplicationContext 实现类指定访问策略。
- 前缀指定访问策略。
ApplicationContext 实现类指定访问策略
当我们创建 ApplicationContext 对象时,通常可以使用如下三个实现类:
- ClassPathXmlApplicatinContext:对应使用 ClassPathResource 进行资源访问。
- FileSystemXmlApplicationContext:对应使用 FileSystemResoure 进行资源访问。
- XmlWebApplicationContext:对应使用 ServletContextResource 进行资源访问。
从上面说明可以看出,当使用 ApplicationContext 的不同实现类时,就意味着 Spring 使用相应的资源访问策略。
当使用如下代码来创建 Spring 容器时,则意味着从本地文件系统来加载 XML 配置文件:
classpath* 的限制?classpath* 前缀仅对 ApplicationContext 有效。实际情况是:创建 ApplicationContext 时,分别访问多个配置文件(通过 ClassLoader 的 getResources 方法实现)。因此,classpath* 前缀不可用于 Reource,使用 classpath*: 前缀一次性访问多个资源是行不通的。
当使用 classpath: 前缀时,系统通过类加载路径搜索 bean.xml 文件,如果找到文件名匹配的文件,系统立即停止搜索,装载该文件,即使有多份文件名匹配的文件,系统只装载第一份文件。资源文件的搜索顺序则取决于类加载路径的顺序,排在前面的配置文件将优先被加载。
另外,还有一种可以一次性装载多份配置文件的方式:指定配置文件时指定使用通配符,例如如下代码:
?
小结
现在,Spring 框架已成为绝大部分框架都争相“拥抱”的对象(现在大部分 Java EE 框架都会提供与 Spring 整合的接口),Spring 框架能发展到今天绝非偶然,很大程度上来自于两方面原因:一方面 Spring 框架既提供了简单、易用的编程接口,因此深得用户拥护;另一方面 Spring 框架自身具有极为优秀的设计,这种优秀的设计保证了 Spring 框架具有强大生命力。对于一个有志于向架构师发展的软件工程师而言,精研 Spring 框架的源码,深入理解 Spring 框架的设计是一个不错的途径。本文主要从策略模式的角度来分析了 Spring 资源访问方面的设计,从而帮助读者更好地理解 Spring 框架。
参考资料
学习
- 本文作者李刚编著的《轻量级 Java EE 企业应用实战》:本书全面介绍了 Struts 2.2、Spring 3.0、Hibernate 3.6 整合开发的知识,并通过实际案例介绍了 3 大框架在实际项目中应用。?
- The Spring Framework - Reference Documentation:全面介绍 Spring 框架功能和用法的参考手册。?
- Spring Framework API 2.5:介绍 Spring 2.5 所有 API 功能和用法的参考文档。?
- developerWorks Java 技术专区:这里有数百篇关于 Java 编程各个方面的文章。?
- developerWorks 按需演示:观看演示,从为初学者准备的产品安装,到为经验丰富的开发人员准备的高级功能。?
讨论
- 加入?developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。
?
- 加入?developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。
- 本文作者李刚编著的《轻量级 Java EE 企业应用实战》:本书全面介绍了 Struts 2.2、Spring 3.0、Hibernate 3.6 整合开发的知识,并通过实际案例介绍了 3 大框架在实际项目中应用。?
- Resource getResource(String location):该接口仅包含这个方法,该方法用于返回一个 Resource 实例。ApplicationContext 的实现类都实现 ResourceLoader 接口,因此 ApplicationContext 可用于直接获取 Resource 实例。策略模式的优势?当 Spring 应用需要进行资源访问时,实际上并不需要直接使用 Resource 实现类,而是调用 ApplicationContext 实例的 getResource() 方法来获得资源,ApplicationContext 将会负责选择 Resource 的实现类,也就是确定具体的资源访问策略,从而将应用程序和具体的资源访问策略分离开来,这就体现了策略模式的优势。
