关于Spring Framework,今年夏天你可能已经听见很多的议论。在本文中,我将试图解释Spring能完成什么,和我怎么会认为它能帮助你开发J2EE应用程序。
另一framework?
你可能正在想“不过是另外一个的framework”。当已经有许多开放源代码(和专有) J2EE framework时,为什么你应该费心读这篇文章,或下载Spring Framework?
我相信Spring是独特的,因为若干个原因:
. 它定位的领域是许多其他流行的framework没有的。Spring关注提供一种方法管理你的业务对象。
. Spring是全面的和模块化的。Spring有分层的体系结构,这意味着你能选择使用它孤立的任何部分,它的架构仍然是内在稳定的。因此从你的学习中,你可得到最大的价值。例如,你可能选择仅仅使用Spring来简单化JDBC的使用,或用来管理所有的业务对象。
. 它的设计从底部帮助你编写易于测试的代码。Spring是用于测试驱动工程的理想的framework。
Spring对你的工程来说,它不需要一个以上的framework。Spring是潜在地一站式解决方案,定位于与典型应用相关的大部分基础结构。它也涉及到其他framework没有考虑到的内容。
尽管它仅仅是一个从2003年2月才开始的开源工程,但Spring有较长的历史根基。这个开源工程是起源自我在2002年后期出版的《Expert One-on-One J2EE设计与开发》书中的基础代码。这本书展示了Spring背后的基础架构。然而,这个基础架构的概念要追溯到2000年的早些时候,并且反映了我为一系列成功的商业工程开发基础结构的经验。
从2003年1月,Spring已经落户于SourceForge上。现在有10个开发人员,其中6是高度投入的积极分子。
Spring的架构性的好处
在我们进入细节以前,让我们看一下Spring可以给一个工程带来的一些好处:
. Spring能有效地组织你的中间层对象,无论你是否选择使用了EJB。如果你仅仅使用了Struts或其他的包含了J2EE特有APIs的framework,你会发现Spring关注了遗留下的问题,。
. Spring能消除在许多工程上对Singleton的过多使用。根据我的经验,这是一个主要的问题,它减少了系统的可测试性和面向对象特性。
. Spring能消除使用各种各样格式的属性定制文件的需要,在整个应用和工程中,可通过一种一致的方法来进行配置。曾经感到迷惑,一个特定类要查找迷幻般的属性关键字或系统属性,为此不得不读Javadoc乃至源编码吗?有了Spring,你可很简单地看到类的JavaBean属性。倒置控制的使用(在下面讨论)帮助完成这种简化。
. Spring能通过接口而不是类促进好的编程习惯,减少编程代价到几乎为零。
. Spring被设计为让使用它创建的应用尽可能少的依赖于他的APIs。在Spring应用中的大多数业务对象没有依赖于Spring。
. 使用Spring构建的应用程序易于单元测试。
. Spring能使EJB的使用成为一个实现选择,而不是应用架构的必然选择。你能选择用POJOs或local EJBs来实现业务接口,却不会影响调用代码。
. Spring帮助你解决许多问题而无需使用EJB。Spring能提供一种EJB的替换物,它们适于许多web应用。例如,Spring能使用AOP提供声明性事务而不通过使用EJB容器,如果你仅仅需要与单个的数据库打交道,甚至不需要JTA实现。
. Spring为数据存取提供了一致的框架,不论是使用JDBC或O/R mapping产品(如Hibernate)。
Spring确实使你能通过最简单可行的解决办法解决你的问题。这些特性是有很大价值的。
Spring能做什么?
Spring提供许多功能,在此我将快速地依次展示其各个主要方面。
任务描述:
首先,让我们明确Spring范围。尽管Spring覆盖了许多方面,但我们已经有清楚的概念,它什么应该涉及和什么不应该涉及。
Spring的主要目的是使J2EE易用和促进好编程习惯。
Spring不重新开发已有的东西。因此,在Spring中你将发现没有日志记录的包,没有连接池,没有分布事务调度。这些均有开源项目提供(例如Commons Logging 用来做所有的日志输出,或Commons DBCP用来作数据连接池),或由你的应用程序服务器提供。因为同样的的原因,我们没有提供O/R mapping层,对此,已有有好的解决办法如Hibernate和JDO。
Spring的目标是使已存在的技术更加易用。例如,尽管我们没有底层事务协调处理,但我们提供了一个抽象层覆盖了JTA或任何其他的事务策略。
Spring没有直接和其他的开源项目竞争,除非我们感到我们能提供新的一些东西。例如,象许多开发人员,我们从来没有为Struts高兴过,并且感到在MVC web framework中还有改进的余地。在某些领域,例如轻量级的IoC容器和AOP框架,Spring有直接的竞争,但是在这些领域还没有已经较为流行的解决方案。(Spring在这些区域是开路先锋。)
Spring也得益于内在的一致性。
所有的开发者都在唱同样的的赞歌,基础想法依然是Expert One-on-One J2EE设计与开发的那些。
并且我们已经能够使用一些主要的概念,例如倒置控制,来处理多个领域。
Spring在应用服务器之间是可移植的。
当然保证可移植性总是一次挑战,但是我们避免任何特定平台或非标准化,并且支持在WebLogic,Tomcat,Resin,JBoss,WebSphere和其他的应用服务器上的用户。
倒置控制容器
Spring的设计核心是 org.springframework.beans 包, 为与JavaBeans一起工作而设计。 这个包一般不直接被用户使用, 但作为基础为更多的其他功能服务. 下一个较高层面的抽象是"Bean Factory"。 Spring bean factory 是一个普通的Factory,它使对象能够按名称获取,并且能管理对象之间的关系。
Bean factories 支持两种对象模式:
. Singleton:在此模式中,有一个具有特定名称的共享对象实例,它在查找时被获取。这是默认的,而且是最为经常使用的。它对于无状态对象是一种理想的模式。
.Prototype:在此模式中,每次获取将创建一个独立的对象。例如,这可以被用于允许用户拥有他们自己的对象。
由于 org.springframwork.beans.factory.BeanFactory是一个简单的接口,它能被为了底层存储方法而实现。你能够方便地实现你自己的BeanFactory,尽管很少用户需要。最为常用的定义是:
.XmlBeanFactory: 可解析简单直观的定义类和命名对象属性的XML结构。 我们提供了一个DTD来使编写更容易。
.ListableBeanFactoryImpl:可提供解析存放在属性文件中的bean定义,和可通过编程创建BeanFactories。
每个bean定义可能是一个POJO(通过类名和JavaBean初始属性定义),或是一个FactoryBean。FactoryBean接口添加了一个间接层。通常,这用于使用AOP或其他方法来创建代理对象:例如,添加了声明性事务管理的代理。(这在概念上和EJB侦听相似,但在实践中实现更简单。)
BeanFactories能在一个层次结构中可选择性的参与,根据来自祖先的继承定义。这使在整个应用中公共配置的共享成为可能,虽然个别资源,如controller servlets,也拥有他们自己的独立的对象集合。
这种使用JavaBeans的动机在<Expert One-on-One J2EE Design and Development>的第四章中有描述,在TheServerSide网站上的有免费的PDF(http://www.theserverside.com/resources/article.jsp?l=RodJohnsonInterview).
通过BeanFactory概念,Spring成为一个倒置控制的容器。(我非常不喜欢container这个术语,因为它使人联想到重量级容器,如EJB容器。Spring的BeanFactory是一个可通过一行代码创建的容器,并且不需要特殊的部署步骤。)
位于倒置控制背后的概念是在Hollywood原则中经常表述:"Don’t call me, I’ll call you." IoC将控制职责搬进了框架中,并脱离应用代码。涉及到配置的地方,意思是说在传统的容器体系结构中,如EJB,一个组件可以调用容器并问“我需要它给我做工作的对象X在哪里?”;使用IoC容器则只需指出组件需要X对象,在运行时容器会提供给它。容器基于方法名作出这种说明,或可能根据配置数据如XML。
倒置控制的几个重要好处。如:
. 因为组件不需要在运行时间寻找合作者,所以他们可以更简单的编写和维护。在Spring的IoC版本里,组件通过暴露JavaBean的setter方法表达他们依赖的其他组件。这相当于EJB通过JNDI来查找,EJB查找需要开发人员编写代码。
. 同样原因,应用代码更容易测试。JavaBean属性是简单的,Java核心的,并且容易测试:仅编写一个包含自身的Junit测试方法用来创建对象和设置相关属性即可。
. 一个好的IoC实现隐藏了强类型。如果你使用一个普通的factory来寻找合作者,你必须通过类型转换将返回结果转变为想要的类型。这不是一个主要问题,但是不雅观。使用IoC,你在你的代码中表达强类型依赖,框架将负责类型转换。这意味着在框架配置应用时,类型不匹配将导致错误;在你的代码中,你无需担心类型转换异常。
. 大部分业务对象不依赖于IoC容器的APIs。这使得很容易使用遗留下来的代码,且很容易的使用对象无论在容器内或不在容器内。例如,Spring用户经常配置Jakarta Commons DBCP数据源为一个Spring bean:不需要些任何定制代码去做这件事。我们说一个IoC容器不是侵入性的:使用它并不会使你的代码依赖于它的APIs。任何JavaBena在Spring bean factory中都能成为一个组件。
最后应该强调的是,IoC 不同于传统的容器的体系结构( 如EJB), 应用代码最小程度的依靠于容器。这意味着你的业务对象可以潜在的被运行在不同的IoC 框架上-或者在任何框架之外-不需要任何代码改。
以我的经验和作为Spring用户,过分强调IoC给应用代码带来的好处是不容易的。
IoC不是一个新概念,但是它在J2EE团体里面刚刚到达黄金时间。 有一些可供选择的IoC 容器: notably, Apache Avalon, PicoContainer 和 HiveMind. Avalon 不会成为特别流行的,尽管它很强大而且有很长的历史。Avalon是相当的重量级和复杂的,并且看起来比新的IoC解决方案更具侵入性。 PicoContainer是一个轻量级而且更强调通过构造器表达依赖性而不是JavaBean 属性。 与Spring不同,它的设计允许每个类型一个对象的定义(可能局限性结果来自它对Java代码外的元数据的拒绝)。作为和Spring and PicoContainer and other IoC frameworks的比较,可参看文章http://www.springframework.org/docs/lightweight_container.html. 这个业面包含了PicoContainer站点链接 。
Spring BeanFactories 是非常轻量级的。用户已经成功地将他们应用在applets中和单独的Swing应用中。(它们也很好地工作在EJB容器中。) 没有特殊的部署步骤和可察觉的启动时间。这个能力表明一个容器在应用的任何层面差不多立即可以发挥非常大的价值。
Spring BeanFactory 概念应用贯穿于Spring整体, 而且是Spring如此内在一致的关键原因。在IoC容器中,Spring也是唯一的,它使用IoC作为基础概念贯穿于整个框架。
对应用开发人员,最重要的是,一个或多个BeanFactory提供一个定义明确的业务对象层。这是类似的,但比local session bean层更简单。与EJBs不同,在这个层中的对象可能是相关的,并且他们的关系被自己的factory管理。有一个定义明确的业务对象层对于一个成功的体系结构是非常重要的。
Spring ApplicationContext 是BeanFactory的子接口,为下列提供支持:
.消息寻找,国际化支持
.事件机制,允许应用对象发布和随意地注册为事件监听
.便携文件和资源访问
XmlBeanFactory示例
Spring用户通常在XML“bean定义”文件中配置他们的应用。Spring的XML bean定义文档的根是一个<beans> 元素。该元素包含一个或多个 <bean>定义。我们一般指定一个bean定义的类和属性。我们也必须指定ID作为标识,我们将在代码中使用该标志。
让我们来看一个简单的例子,在J2EE应用中常看到用来配置三个应用对象:
. J2EE DataSource
. 使用DataSource的DAO
. 在处理过程中使用DAO的业务对象
在下面的例子中,我们使用一个来自Jakarta Commons DBCP项目的BasicDataSource。这个class(和其他存在的class一样)可以简单地被应用在Spring bean factory中,因为它提供了JavaBean格式的配置。需要在shutdown时被调用的Close方法可通过Spring的"destroy-method"属性被注册,来避免BasicDataSource需要实现任何Spring 接口。
代码: |
|
我们感兴趣的BasicDataSource的所有属性都是String型的,因此,我们用<value>元素来指定他们的值。如果必要的话,Spring使用标准的 JavaBean属性编辑器机制来转换String以表示其他的类型。
现在,我们定义了DAO,它有一个对DataSource的bean引用。Bean间关系通过<ref>元素来指定:
代码: |
|
业务对象有一个DAO的引用和一个int型属性(exampleParam):
代码: |
|
对象间的关系一般在配置中明确地设置,象此例子一样。我们认为这样做是件好事情。无论如何,Spring也提供了我们叫做"autowire"的支持, 一个la PicoContainer,在那里,它可以指出bean间的依赖关系。这样做的局限性-如使用PicoContainer-是如果有一个特殊类型的多个Bean,要作出一个类型应该与哪个实例相关的判断将是不可能的。好的方面,在factory初始化后,不理想的依赖可能会被捕获到。(Spring 也为清楚的配置提供一种可选的依赖检查,它可以完成这个目的)
如果我们不想明确的编写他们的关系,在上面的例子中,我们可如下使用autowire特性:
<bean id="exampleBusinessObject" class="example.ExampleBusinessObject" autowire="byType">
<property name="exampleParam"><value>10</value></property>
</bean>
使用这用用法,Spring将找出exampleBusinessObject的dataSource属性应该被设置为在当前BeanFactory中找到的DataSource实现。在当前的BeanFactory中,如果所需要类型的bean不存在或多于一个,将产生一个错误。我们依然要设置exampleParam属性,因为它不是一个引用。
Autowire支持和依赖检查刚刚加入CVS并将在Spring 1.0 M2(到10/20,2003)中提供。本文中所讨论的所有其他特性都包含在当前1.0 M1版本中。
来自Java代码的外在关系比硬编码有极大的好处,因为改变XML文件而无需改变一行Java代码是可能的。例如,我们可以简单地改变myDataSource的bean定义来提供不同的bean class以使用一个可供选择的连接池,或者一个测试数据源。 在一个单独可选的XML片断中,我们可以用Spring的JNDI 定位FactoryBean来从application server获取一个数据源。
现在让我们来看例子中业务对象的java 代码。注意下面列出的代码中没有对Spring的依赖。不像EJB容器,Spring BeanFactory不是侵入性的:在应用对象里面你通常不需要对他们编码。
代码: |
|
注意属性设置器,在bean定义文档中,它对应与XML引用。这些将在对象使用之前被Spring调用.
这些应用bean不需要依赖于Spring。他们不需要实现任何Spring接口或者继承Spring的类:他们只需要遵守JavaBeans命名习惯。在Spring 应用环境之外重用它们是非常简单的,例如,一个测试环境。只是例示它有默认的构造器,并且通过setDataSource()和setExampleParam()的调用来手工设置它的属性。只要你有一个没有参数的构造器,如果你想在单行代码支持程序化的创建,你可以自由定义其他的带有多个属性构建的器。
注意,在业务接口中没有声明的JavaBean属性将会一起工作。 他们是一个实现细节。我们可以插入带有不同bean属性的不同的实现类而不影响连接对象或者调用代码。
当然,Spring XML bean factories 有更多的功能没有在这里描述,但是,这将给你一种基本使用的感觉。同时,简单的属性和有JavaBean属性编辑器的属性,Spring可以自动处理lists,maps和java.util.Properties.