Spring 资源访问辨析和策略模式应用(转)

Spring 资源访问剖析和策略模式应用(转)?Spring 把所有能记录信息的载体,如各种类型的文件、二进制流等都称

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 用户交流。

                    ?