如果您是一名典型的开发人员,那您必然乐意得到一个可解决数据访问问题的解决方案,也会欢迎任何能够简化配置的工具。如果不是有人提出了
Spring,则对 Web 应用程序做一个总体介绍将非常困难,对于这些特定的问题更是如此。但是,天哪,我们真的需要另外一种
Web 应用程序框架吗?在我决定撰写一期 Geronimo 叛逆者文章,来探讨 Apache Geronimo 和 Spring
的交叉时,我就知道,这正是搞清所有关于 Spring 框架的传闻的大好时机。Jeff Genender 为我解答了很多难题,我完成了这个任务。
控制反转使配置更轻松
通过求助于 Jeff Genender、Geronimo committer 和周围的能人,我开始了寻求有关
Spring 框架的所有问题的答案的旅程。我替那些不熟悉 Spring 的人提出了这样的问题:Spring 究竟为何物?
稍做调查后,我了解到,Spring 是一种 Web 应用程序 API,它包含了模型-视图-控制器(Model-View-Controller,MVC)模式的实现,供那些不喜欢
Struts 的人使用。但是到底是什么本事使其声名显赫?哪种至关重要的特性为此框架提供了关键部分?
“Spring 是一个 IoC 容器”,Jeff 解释道,“那代表控制反转(Inversion
of Control),使您能够注入在 XML 文件中声明的依赖项。”
IoC 对我来说是一个新术语,因此 Jeff 解释说,创建应用程序时通常会拥有依赖另一个对象的对象。例如,您拥有一个表示三明治制造机的对象,该对象引用了另一个对象:三明治装填机。因此您可能拥有下面这样的代码(参见清单
1)。
清单 1. 一个样例类
package com sandwiches;
public class SandwichMaker implements FoodMaker {
private SandwichFiller filler;
private String currentSandwich;
public void setSandwichFiller(SandwichFiller filler) {
this.filler = filler;
}
public void setNameOfSandwich(string currentSandwich) {
this.currentSandwich = currentSandwich;
}
public void makeSandwiches() {
//make sandwiches using the SandwichFiller
}
}
|
当然,实际的 SandwichFiller 将取决于您想要制造何种类型的三明治。因此百吉饼商店和大型三明治商店的
SandwichFiller 类实现可能会有所不同。实例化 SandwichMaker
类时,您当然可通过调用 setSandwichFiller() 方法提供 SandwichFiller
的适当实现。但随后需要更改代码,在新位置安装 SandwichMaker 。Spring 让您能够创建包含这些依赖项定义的应用程序上下文。(因此有时将此称作依赖项注入。)
文件可能形入清单 2 所示。
清单 2. ApplicationContext.xml 文件
<beans>
<bean id="bagel" class="com.sandwiches.BagelShop">
<property name="breadPreference" value="bagel" />
<property name="diameter" value="5" />
</bean>
<bean id="ccandjFiller" class="com.sandwiches.CreamCheeseAndJellyFiller">
<property name="sandwichType" ref="bagel" />
<property name="creamCheesePortion" value="60" />
<property name="jellyPortion" value="40" />
<property name="jellyFlavor" value="grape" />
</bean>
<bean id="sandwichMaker" class="com.sandwich.SandwichMaker">
<property name="sandwichFiller" ref="ccandjFiller" />
<property name="nameOfSandwich" value="Cream Cheese and Jelly" />
</bean>
</beans>
|
好,让我们更仔细地观察它一下,从底部开始。我们已告知环境,实例化 com.sandwich.SandwichMaker
类时,sandwichFiller 属性应由标识为 ccandjFiller
的 bean 来填充。该 bean 是一个 com.sandwiches.CreamCheesAndJellyFiller ,有着自己的属性。它的
sandwichType 为 bagel ,后者将 breadPreference
设置为 bagel (而不是 rye 或 bialy),并且其宽度为 5 英寸。另外,我们希望奶酪比葡萄果冻稍微多一点。
现在看来,这里有着太多的自定义工作;如果您想将那些项都编写到应用程序中去,则要在程序更改时进行大量的重编译工作,或在选项中进行大量的构建工作以做出所有那些选择。Spring
通过只更改 XML 文件而使我们能更轻松地做出更改。在此我选择了一个比较简单的示例,但可以想像一下,您正在配置数据源,这个 IoC
立即变得至关重要。
数据库的优点
提到数据源,Jeff 解释说这是另一个领域,其中 “Spring 拥有扩展了很多 J2EE(和非
J2EE)组件的优秀 API”,使开发人员更加轻松。
“在 Spring 中创建数据访问对象相对较为简单”,他解释说。“使用 JDBC(或其他框架,如
Hibernate)扩展几个 Spring 类,实现 DAO,然后在 applicationContext.xml 文件中做出声明。此后可通过声明的方式将这些
DAO 注入业务对象,方法是为 DAO 设定一个 setter 方法并在 Spring XML 文件中声明业务对象。”
从交谈中,我学到了如此之多的 IoC 相关知识。但不止于此:“这样做,您就可以摆脱捕获
SQLExceptions 的要求,并使【对数据库的】测试与交换声明一样简单。”
我明白后半部分;数据库是一个可在 applicationContext.xml 文件中轻松设置的属性。但它是如何使您摆脱捕获
SQLExceptions 的要求的呢?
“Spring 将 SQLExceptions 等转换为未检查异常,从而隐藏了
JDBC SQL 问题。这随后又清除了错误,从而使问题一目了然。这是处理数据库异常的一种更好的方法。”
大致说来,创建 JDBC 应用程序时,如果出现问题,您将得到 SQLException 。遗憾的是,该
SQLException 往往不会包含实际查找出问题所在而必需的全部信息,因为实际信息被编码为特定于供应商的错误码,您必须对其进行跟踪。当然,由于此处设计思想的一部分是可轻松地
更改数据源,因此将智能编码到应用程序中是不可接受的。但是,通过将数据库交互包装到 Spring 类中,如 JdbcTemplate ,可确保将所有错误表达为
DataAccessException ,或更重要地,表达为 DataAccessException
的十余个子类之一,包括 DataAccessResourceFailureException 、DataIntegrityViolationException
和 OptimisticLockingFailureException 等。这样您就可以更灵活地编写应用程序。
隐藏类防止冲突
至此,我们已经介绍了大量关于 Spring 的信息,但关于 Geronimo 呢?团队是否要做什么特殊的工作才能将其集成到应用服务器中?
“我记得 Bruce Snyder 和 Aaron Mulder 为其 Apachecon 2006
插件演示编写了 Spring 插件,但除那之外,Spring 实际上是一个 API,因此对于 Spring,将其放入存储库中,它就可供所有程序使用。”
换句话说,无需执行任何特殊操作;只要 Spring 位于存储库中,那么它就是可用的。
一切并没有结束。Jeff 继续说道,“对于其他应用服务器,人们最大的抱怨就是:需要将 Spring
放在服务器的类路径中,而此时也将其包含在了 Web 应用程序中”—— 如果应用程序使用的 Spring 和服务器安装的 Spring
版本不同则需进行此操作 —— “将会与当前使用的 Spring 发生冲突。这是 J2EE 应用程序的父-子、子-父类加载二分法的一部分。”
问题在于某些情况下,Java™ 2 Platform, Enterprise Edition
(J2EE) 或 Java Platform, Enterprise Edition (Java EE) 在最特殊的级别启动,寻找一个类进行实例化(在该情况下系统将使用作为应用程序一部分的类),而在其他情况下,系统使用最常规的级别(此时系统将使用作为服务器一部分的类)。结果是:您可能在应用程序的上下文中创建了一个资源,但当您尝试访问该资源时,服务器将在应用服务器的上下文中进行查找。
“这将使您的应用程序找不到其 Hibernate 或其他一些资源,因为它们是在一个 Spring
容器中创建而在另一个容器中访问的。”
最初,Geronimo 开发人员通过自动隐藏某些通用类解决了此问题,比如 Spring and
Apache Commons Logging 类,它经常会导致此问题。但 Liferay 使情况发展到了极点,Liferay 是一种开源
JSR 168 兼容门户(参见 参考资料 中的链接)。Liferay 使用 Spring,它在
.ear 文件中包含了一个 Spring 版本供其中部署的所有子应用程序使用。“我提到它们的原因在于”,Jeff 告诉我说,“我们隐藏的类加载架构在早期导致了一个问题,因为我们自动隐藏了
Spring,所以它们的 Web 应用程序无法看见 Spring 的 EAR 级版本。”
最后,Geronimo 开发人员找到了一个解决方案。“但是,我们拥有一个比大多数其他应用服务器更好的东西。许多人遇到了关于通用日志的问题。当他们在使用通用日志的容器内运行程序(如
JBoss、Tomcat 等),并且在其应用程序中包含了通用日志 JAR【文件】时,将遭遇严重的异常问题。”这些问题与做出初始更改前
Spring 应用程序遇到的问题相同。“它实际上是一个类加载器冲突的问题。我们遇到了一些开放式的问题,表明 Web 应用程序没有正常运行,但实际上它们正常运行了;只需删除通用日志,就像需为
Tomcat 和 JBoss 等删除日志一样。因此我们进行了大量的讨论,并开始硬编码如通用日志等模块类。接着再继续讨论,我们找到了一种更好的声明性方法。因而出现了隐藏类声明。我们能在计划文件中声明隐藏类,您将用之来部署应用程序。”
“它运作得很好”,他继续说道,“因此我们没有遇到许多其他容器所具有的问题。您控制了父-子、子-父委托的类加载器。因此利用
Geronimo 可声明应用程序只 使用其中所包含的 Spring,忽略服务器版本,反之亦然。它的另一个优点是可使用多个版本的
Spring。因此即便服务器使用 2.0 版本而您的遗留应用程序使用的是 1.x 版本,您也可确保不会发生任何冲突,并且应用程序将使用适当的版本。”
为 Apache Geronimo for Spring 添加更多内容
虽然 Spring 可能已迫使团队做出了这些更改,但它们对任何类都有用这一点可能会成为一个问题。但这并不意味着特定于
Spring 的工作有一天不会在 Geronimo 中实现。
“刚刚我在跟 Bruce Snyder 交谈”,Jeff 挂断电话后告诉我说,他接电话时我迅速地吃了点东西。“我们在谈论构建或扩展一个插件,以允许您将
Spring ApplicationContexts 放入 JNDI,从而可在服务器端访问基于 Spring
的对象。我已为一台客户机编写了这样的插件,我认为它应是应用服务器的核心功能。只是要抽时间来办这件事”,他笑着说道。
“在某种程度上我们会这样做”,他说,“为应用程序提供基于服务器的 Spring 对象是个不错的想法。您可以存储希望应用程序可访问的那些特定的
Spring 对象或对象引用。例如,如果您正在使用 Quartz,并需要容器的引用,并且通过 Spring 创建对象,则您可能需要拥有该对象的引用的
ApplicationContext 的访问权限,因而它需要广泛可用。将其存储在 JNDI 中可使您的应用程序能够访问另一个上下文中启动的其他
ApplicationContexts 。这对于面向服务的架构 —— 和基于企业服务总线的应用程序(我以前正是在其中开发此程序)来说将是件大事。我简单猜测,将其存放得更直接,有益于那些没有连接到公共资源,但需要访问这些资源的应用程序。那是存储它的最佳方式。SOA
应用程序需要此功能。”
结束语
我们又谈论了一会 Geronimo committer 和社区正努力从事的所有工作,然后我就离开了,最后,我终于明白了所有那些问题。Spring
似乎是一种很好用的框架。很明显,当应用程序需做出任意的配置更改时,IoC 和依赖项注入使我能更轻松地进行处理,我已经浪费了太多时间去尝试解释
SQLExceptions ,而不是去欣赏 Spring 的数据访问方法。更重要的是,我学到了关于类加载器的知识,还搞清楚了为什么我有时会遇到那种似乎任何方法都解决不了的错误,因此我从心底感激
Geronimo 的隐藏类声明。
接下来,我准备去找人解释一下 JavaServer Faces (JSF) 技术是怎么回事。
参考资料
学习
获得产品和技术
讨论
|