摘要
J2EE编程正在变得越来越复杂。J2EE已经发展为一个API、复杂化的编程和配置的复杂网络。为了应对这种复杂性,新的框架和方法不断涌现。这些框架高度依赖于一个称为IoC(Inversion
of Control,反向控制)的概念。本文将探讨这种方法的一些特性和优点,因为这种方法与J2EE编程相关,而且可以使J2EE编程变得更轻松。
简介
马克·吐温的一句话常被引用:“……关于我死亡的报道是一种夸张。”现在已经出现了很多关于.Net的流言,以及认为J2EE API的复杂性无法克服和EJB作为一种组件架构即将灭亡的流行极客(geek)文化。从学术或者只是想像的立场来看,这没什么大不了的,但事实是J2EE/EJB
API已经经历了一场达尔文式的进化。具有DCOM或CORBA项目经验的读者会明白我的意思。过去,人们都乐于听闻EJB组件模型的美好前景。实际情况是,人们在与J2EE相关的各个方面都投入巨大。宣布抛弃以前的所有工作并重新组织,这种想法看起来也许有理,但是它并没有建立在良好的业务洞察力之上。EJB继续发展,而术语、实践和框架也随之涌现(spring
up),它们弥补了J2EE API的不足。我说的不是“Spring出现(up)”,对吧?
我是一名顾问,职责是帮助构建大型的分布式应用程序,而且通常是J2EE应用程序。因此,我有机会亲历许多项目的整个生命周期。另外我还能够将我从一个刚刚完成的项目中刚刚学到的东西直接带入一个全新的项目。从某种意义上说我的“自然选择”过程加快了。我可以说最近Spring(更具体地说就是IoC,即反向控制)已经越来越多地融入到我的项目中了。在本文中,我将从支持或增强J2EE项目的角度来探讨Spring。更确切地讲,Spring框架能够标准化许多J2EE最佳实践,还能同类化(homogenize)许多无处不在的J2EE模式。接下来我们将浏览Spring庞大体系中的一小部分内容,重点介绍(依我浅见)能够帮助改进J2EE应用程序的功能。
IoC简介
一般来说,IoC是一种管理类之间关联的技术。没错,就这么简单!任何人都不是孤立的,对于各个对象来说也是如此。应用程序中的对象是相互依赖的。通过编程方式来表现这种依赖性通常既冗长又容易出错。好的IoC框架将声明式地(通过一个XML配置文件)而不是编程式地(这种方式的可靠性较差)——串连起应用程序之间的相互依赖性。
自由使用接口是IoC开发的一个主要方针。接口编程大大提高了应用程序的灵活性,从而增强了声明式的关联。接口实现是通过IoC配置在运行时声明的,这样就能够在不影响或少影响实际应用程序代码的情况下“重建(rewire)”关联。这在各种IoC框架中是反复提及的一个主题,一般而言,也是应该遵循的良好实践。
一个小例子
我喜欢通过例子来更快地理解概念。下面就是运用了IoC的一组例子;您将看到,这些例子的复杂性是逐递增的。大多数人在一开始使用IoC容器时都是利用其依赖注入(inject
dependency)功能——即,声明式地将对象关联起来。利用IoC有助于创建更整洁的代码,如有必要重建对象之间的关联,一般来说对于这些代码也会更灵活、更容易。IoC的优点远不止依赖注入,而其扩展功能确是以依赖注入程序为起点的。
我们将从构建简单的依赖注入例子开始。第一个例子用于阐明已经提及的两个概念。第一个概念是IoC在运行时构建和关联对象的能力,第二个是与接口编码相结合而产生的灵活性。首先假定架构师递交了图1所示的UML。
图1. 接口可插性
这个小例子表示一个温度测量系统。几个传感器对象属于不同的类型,但都实现了ProtocolAdapterIfc接口,因此在将它们插入TemperatureSensor对象时,它们是可互换的。在需要TemperatureSensor时,系统中的某个实体必须知道要生成并与该传感器对象关联的ProtocolAdapterIfc的具体类型。在本例中,该传感器可基于命令行参数、数据库中的行或通过属性文件进行配置。本例还不足以造成挑战或展示一个复杂框架,但它足以阐明IoC基础。
但是,想象一下:在一个相当复杂的应用程序中这种情况屡屡发生,而您还希望能动态地——至少要在外部——改变对象关联。假设有一个DummyProtocolAdapter,它总是返回42这个值,使用它来进行测试。为什么不提供一个单个的统一框架?——让开发人员能够依靠该框架,以一种一致的、外部配置的方式建立类之间的关联,并且不引起工厂单元素类(factory
singleton classe)的异常增加。这听起来可能没什么大不了,但它要依赖于IoC的简单性。
我们使用一个TemperatureSensor类,它与一个实现ProtocolAdapterIfc接口的类有关联。TemperatureSensor将使用该委托类来获得温度值。如UML图所示,在实现ProtocolAdapterIfc并且随后可用于该关联的应用程序中有若干个类。我们将使用IoC框架(在本例中是Spring)来声明要使用的ProtocolAdaperIfc的实现。Spring将在运行时建立关联。我们先来看XML代码,它将实例化TemperatureSensor对象并将一个ProtocolAdapterIfc实现与它关联起来。该代码如下所示:
<bean id="tempSensor"
class="yourco.project.sensor.TemperatureSensor">
<property name="sensorDelegate">
<ref bean="sensor"/>
</property>
</bean>
<!-- Sensor to associate with tempSensor -->
<bean id="sensor" class="yourco.project.comm.RS232Adapter"/>
看了这些代码之后,对于其目的就应该非常清楚了。我们配置Spring来实例化TemperatureSensor对象,并将其与RS232Adapter相关联,作为实现ProtocolAdapterIfc接口的类。若想改变已经与TemperatureSensor关联的实现,惟一需要更改的就是sensor
bean标记中的class值。只要实现了ProtocolAdapterIfc接口,TemperatureSensor就不再关心关联了什么。 将这应用于应用程序相当简单。我们必须先接入Spring框架,将它指向正确的配置文件,然后根据名称向Spring索取tempSensor对象的实例。下面是相应的代码:
ClassPathXmlApplicationContext appContext =
new ClassPathXmlApplicationContext(
new String[]
{ "simpleSensor.xml" });
BeanFactory bf = (BeanFactory) appContext;
TemperatureSensor ts = (TemperatureSensor)
bf.getBean("tempSensor");
System.out.println("The temp is: "+
ts.getTemperature());
可以看出,这些代码并不是非常难。首先是启动Spring并指定要使用的配置文件。接下来根据名称(tempSensor)引用Bean。Spring使用这样一种机制:基于simpleSensor.xml文件的描述创建该对象并与其他对象关联。它用于注入依赖性——在本例中,通过将它作为一个参数传递给sensorDelegate()方法而实例化RS232Adapter对象并将其与TemperatureSensor对象关联。
比较起来,使用编程式Java完成这一任务也不是很难。如下所示:
TemperatureSensor ts2 = new TemperatureSensor();
ts2.setSensorDelegate(new RS232Adapter());
纯粹主义者或许会认为实际上这是更好的方法。代码行数少,并且可读性可能更强。确实如此,但这种方法的灵活性要小得多。
可以随意换入和换出不同层中不同对象的不同实现。例如,若Web层中的组件需要来自新业务对象的额外的功能,您只需将该业务对象与Web层对象相关联,就像上面TemperatureSensor例子中的做法。它将被“注入”到Web对象中以随时使用。
能够重新配置整个应用程序的结构,意味着可以轻松更改数据源。比如说,或者为不同的部署场景创建不同的配置文件,或者为测试场景创建更有用的、不同的配置文件。在测试场景中可能会注入实现接口的模拟对象,而不注入真正的对象。稍后我们将介绍一个这样的例子。
上面所述的例子可能是依赖注入的最简单形式。利用相同的策略,我们不仅能够关联不同的类,还能够在类中安装属性。诸如字符串、整数或浮点数之类的属性,只要具有JavaBean样式的存取器,就可以通过Spring配置文件将它们注入类中。我们还可以通过构造函数来创建对象和安装属性或bean引用。其语法只比通过属性进行设置稍稍复杂一些。
所有这一切都是利用一种灵活的声明性配置完成的。无需更改代码,建立依赖关联的所有艰难任务都由Spring来完成。
Spring--标准化的定位器模式
我一直将服务定位器模式视作良好的J2EE规范的主要组成部分。对于不熟悉这一术语的人来说,可以这样理解它:我们一般认为典型的J2EE应用程序由若干层组成。通常有Web层、服务层(EJB、JMS、WS、WLS控件)以及数据库。一般来说,完成某一请求所需的“查找”服务中都包含了一些方法。Service
Locator(服务定位器)模式认为,将这些方法包装在某种隐藏了生成或查找给定服务的复杂性的工厂类中是一个好主意。这减少了JNDI或只会造成Web层操作类混乱的其他服务产品代码的增加。在Spring出现以前,这通常是由经过考验证明可靠的(tried-and-true)Singleton类来实现的。Singleton/Locator/Factory模式可以描绘为:
图2. 定位器模式的顺序图
这是对散布在整个Web控制器代码中的增加的JNDI查找代码的一个巨大改进。它被巧妙地隐藏在工厂内部的协作类中。我们可以使用Spring来改进这一术语。此外,该解决方案将适用于EJB、Web
services、异步JMS调用,甚至还有基于WLS控件的服务。由Spring实现的这种定位器模式的变体考虑了业务服务之间的一些抽象化和同质性。换句话说,Web控制器的开发人员真的可以不考虑他们所使用的服务的种类,一个类似于“WLS控件”但是更通用的概念。
IoC框架大大改进了这种模式的效用,而且实际上废除了复杂而特殊的singleton代码来实现它。通过借用上例中引入的概念,我们实际上无需额外代码便能构建一个非常强大且无处不在的Service
Locator模式。为此,在一开始有一个简单的要求,即Web操作的开发人员应专门处理实现接口的那些事情。这基本上已经通过EJB编程实现,但并不是说Web操作的开发人员处理的服务必须通过EJB来实现。它们可能只是普通Java对象或Web
services。要点是应当通过接口(这样实现能够换入换出)来编写服务程序,并且运行时配置能够由Spring处理。
Spring之所以非常适合于Service Locator模式,是因为它或多或少能够统一地处理不同类型的对象。通过少许的规划和大量使用IoC,我们多少都能够以一种通用方式来处理大多数对象,而不用管它们的特性(EJB、POJO等等)如何,并且不会引起Singleton工厂类的增加。这使Web层编程变得更加轻松和灵活。
我们先来看一个关于这种模式如何应用于EJB的例子。我们都知道使用EJB可能是最复杂的方法,因为要将一个活动的引用引入EJB要做很多工作。若使用Spring,建议用EJB接口扩展非特定于EJB的业务接口。这样做有两个目的:保持两个接口自动同步,以及帮助保证业务服务对非EJB实现是可交换的,以便进行测试或清除(stubbing)。我们可以利用Spring固有的实用工具来定位和创建EJB实例,同时为我们处理所有难以处理的工作。相应代码如下所示:
<bean id="myBizServiceRef"
class="org.springframework.ejb.access.
LocalStatelessSessionProxyFactoryBean">
<property name="jndiName">
<value>myBizComponent</value>
</property>
<property name="businessInterface">
<value>
yourco.project.biz.MyBizInterface
</value>
</property>
</bean>
接下来可以检索bean并开始使用它,方法如下:
MyBizInterface myService = bf.getBean("myBizServiceRef");
这将返回Spring动态创建并包装了底层目标(在本例中是一个本地EJB实例)的一个对象。这种方法非常好,因为它完全隐藏了我们在处理EJB这一事实。我们将与一个实现简单业务接口的代理对象交互。Spring已经基于“真正的”业务对象考虑周到地动态生成了该对象。所包装的对象当然就是Spring定位和检索引用所要获得的本地EJB。此外,您还会注意到,这种代码形式与前面用于检索tempSensor对象的代码完全相同。
那么如果我们改变主意,想用普通Java对象来实现业务组件;或者可能在测试中,我们想用一个返回“固定(canned)”响应的已清除(stubbed)对象来替换重量级EJB,该怎么做呢?利用IoC和Spring,通过更改Spring上下文文件就可轻而易举地实现这些目标。我们只需使用更常规一点的东西(如我们在第一个Spring例子中所看到的)来替换EJB代理的连接即可:
<bean id="myBizServiceRef"
class="yourco.project.biz.MyStubbedBizService">
</bean>
请注意,我只更改了Spring框架所返回的内容的细节,没有更改bean id。最后的结果是业务对象的解决方案未变;它看上去和以前完全一样:
MyBizInterface myService =
bf.getBean("myBizServiceRef");
最大的区别显然是实现该业务接口的对象现在由一个普通Java对象(POJO)支持,并且只是该接口的一个已清除(stubbed)版本。这给单元测试或改变业务服务的特性带来了极大方便,而对客户端代码的影响很小。
使用Spring来标准化异常
Spring的一大贡献是“模板化”代码块。这在纯JDBC编程中表现得最为明显。我们都曾写过具有下述功能的代码:
创建一个数据库连接,可以的话从某个池创建。
构造一个查询字符串并提交。
迭代结果并将数据封送到域对象中。
处理不同阶段出现的大量异常。
确保记得编写finally代码块以关闭连接。
但是各处的这种代码往往都会或多或少地有点“样板化”。一般来说这是有害的,不仅因为不需要的代码会增加,还因为有些东西可能会遗漏,如非常重要的关闭连接,如果没有实现它,可能导致数据资源池的泄漏。
虽然我敢肯定我们都曾多次写过这类“样板”代码,但是将Spring方法和直接的JDBC实现对照来看,其结果将会有趣而又对比鲜明。“传统”的JDBC实现可能如下:
Connection con = null;
try
{
String url = "jdbc://blah.blah.blah;";
con = myDataSource().getConnection();
Statement stmt = con.createStatement();
String query = "SELECT TYPE FROM SENSORS";
ResultSet rs = stmt.executeQuery(query);
while(rs.next()){
String s = rs.getString("TYPE);
logger.debug(s + " " + n);
}
} catch(SQLException ex)
{
logger.error("SQL ERROR!",ex);
}
finally
{
con.close();
}
对于该方法要做一些说明。首先,它是有效的!该代码绝对不会出现任何错误。它会连接到数据库,并从‘SENSOR’表获取所需的数据。该方法的基本问题源于缺乏抽象化。在大型应用程序中,必须反复剪切和粘贴这段代码,或者至少会出现类似的其他情况。较大的问题在于它依赖于编程人员去做“该做的事”。我们都知道,不管数据库操作的结果是什么,都必须用finally语句来关闭该数据库。有时我们忘记做该做的事。我和所有人一样感到内疚!
编写一个小框架来解决这一问题会非常容易。我相信大家也都曾这样做过,但为何不让Spring帮我们处理这一问题呢?我不敢保证我能想出一个更整洁、更优雅的解决方案。我们来看Spring框架是如何处理这种样板JDBC场景的。
Spring支持各种各样的JDBC、Hibernate、JDO和iBatis模板。模板采用样板概念,并将它转换为合法的编程术语。例如,下面的代码片断封装了上面列出的各个步骤:
DataSource ds = (DataSource) bf.getBean("myDataSource");
JdbcTemplate temp = new JdbcTemplate(ds);
List sensorList = temp.query("select sensor.type FROM sensors",
new RowMapper() {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException;
return rs.getString(1);
}
});
这段简短的代码消除了JDBC编程的冗长,代表了前面提及的样板的思路。请注意我们使用了Spring的IoC来查找该查询的数据源。Spring还支持对已检查异常使用未检查异常;因此许多已检查的JDBC异常会重新映射到通常更有用而且更友好的未检查异常层次结构中。在Spring的上下文文件中配置该数据源类似于下面代码:
<bean id="myDataSource"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName">
<value>org.gjt.mm.mysql.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://romulus/sensors</value>
</property>
<property name="username">
<value>heater</value>
</property>
<property name="password">
<value>hotshot</value>
</property>
</bean>
在本例中,我们利用Apache commons工具箱配置了一个基本数据源。但并不是说我们只能使用它。我们可以改变配置,使用在JNDI中配置并装载的数据源。Spring提供了一些实用工具和IoC功能以配置和返回存储在JNDI中的对象。例如,若想配置并使用与JNDI上下文关联的数据源,则可以输入以下代码,替换先前的数据源配置:
<bean id="myDataSource"
class="org.springframework.jndi.
JndiObjectFactoryBean">
<property name="tempSensorDS">
<value>ConnectionFactory</value>
</property>
</bean>
该代码突出了Spring所提供的关于表格的测试灵活性。该代码可以在“容器内”运行(从JNDI查找数据源),经过细微的改动之后也可在“容器外”运行。
虽然模板化机制仅适用于某些特定场合,但我们可以泛化这一概念,将它应用于更广泛的场合。例如,一种将已检查异常转变为未检查异常、此外还可能为异常处理提供一些中间或统一的异常处理策略的机制将会很有帮助。我们还可以使用该机制将底层的基本异常“软化”得更合乎人意。我们可以将PacketFrameParityFaultException软化为CommunicationsUnreliableException。这个重新映射的较软的异常表示情况可能并不那么严重,重新请求也是可以的。
Spring已经具备了一种类似于包装EJB调用(在最后一节介绍)的机制,但遗憾的是它并不具备任何“通用”的东西,至少在异常软化意义上是这样的。但Spring的确有一个非常健壮的AOP(面向方面编程)框架,我们可以用它来逼近这种行为。下面是一个关于这种软化适用领域的(公认的)精心设计的例子。
我们再来看看本文前面已经开始探讨的一些概念。在第一节中我们介绍了一个基于远程传感器的小应用程序。现在我们继续探讨这个例子。我们将从一个简单的传感器接口开始介绍,该接口代码如下:
public interface ProtocolAdapterIfc
{
public Integer getRemoteSensorValue()
throws CommChecksumFault,
CommConnectFailure,
CommPacketSequenceFault;
}
这并没有什么特别之处。显然实现该接口的任何人都会获得一个远程值并将它返回给调用者。在此期间调用者可能要面对某些可怕的灾难,该接口可能抛出的已检查异常就是例证。
接下来我们来看该接口的一个实现程序。实现ProtocolAdapter的类是CarrierPigeon,其代码类似于:
public class CarrierPigeon
implements ProtocolAdapterIfc
{
private boolean isTired = true;
private boolean canFlapWings = false;
public Integer getRemoteSensorValue()
throws CommChecksumFault,
CommConnectFailure,
CommPacketSequenceFault
{
if(isTired && !canFlapWings )
{
throw new
CommConnectFailure("I'm Tired!");
}
return new Integer(42);
}
}
为简洁起见,这里省略了属性的getter和setter方法。当调用getRemoteSensorValue()时,CarrierPigeon方法将检查它是否使用过度以及它还能否执行。如果它确是使用过度并且不能执行,我们将无法获得任何值,必须抛出CommConnectionFailure,它是一个已检查异常。到现在为止,一直都还不错。但别高兴得太早了!我已经决定不让应用程序编程人员应对“疲劳的信鸽”。可以将它包装在某种对象中,并在达到目的之前捕获异常,但必须处理20种不同的传感器。此外,我更喜欢对这些东西进行适度透明地处理,随着对该系统了解的增多,或许还会更改异常处理程序中的逻辑。我想要的是一个兼容API,应用程序编程人员能够使用它,并且随着时间的推移能够改变或增强。我想模板化异常处理,并让应用程序编程人员能够处理软的未检查异常,而不是硬异常。Spring非常符合这种情况。下面是为此而使用的策略的要点:
定义一个较软的消除已检查异常的最小接口。这就是应用编程人员将使用的接口。
使用Spring AOP结构,开发一个客户机调用和目标对象调用之间的拦截器,在本例中是“信鸽”。
使用Spring安装该拦截器并运行。
首先来看这个较软的接口:
public interface SensorIfc
{
public Integer getSensorValue();
}
请注意,在限定范围内可以重命名方法,使其更有意义。还可以消除已检查异常,就像这里所做的一样。接下来,也可能会更有趣的是我们想要让Spring将其注入调用栈的拦截器:
import org.aopalliance.intercept.MethodInterceptor;
public class SensorInvocationInterceptor
implements MethodInterceptor
{
public Object invoke(MethodInvocation
invocationTarget) throws Throwable
{
// Return object reference
Object o = null;
// Convert it to the protocol interface type
ProtocolAdapterIfc pai =
(ProtocolAdapterIfc) invocationTarget.getThis();
try
{
o = pai.getRemoteSensorValue();
}
catch (CommChecksumFault csf)
{
throw new SoftenedProtocolException(
"protocol error [checksum error]: "
+ csf.getMessage());
}
catch (CommConnectFailure cf)
{
throw new SoftenedProtocolException(
"protocol error [comm failure]: "
+ cf.getMessage());
}
catch (CommPacketSequenceFault psf)
{
throw new SoftenedProtocolException(
"protocol error [message sequence error]"
+ psf.getMessage());
}
return o;
}
}
通过实现Spring MethodInterceptor接口并将该类插入Spring系统(稍后我们将进行讨论),我们将通过MethodInvocation参数获得实际的目标方法。这样就能提取我们想要的真正对象。请记住,我们更改了被调用者看作SensorIfc的接口,该接口使目标对象得以实现ProtocolAdapterIfc。我们这样做是为了简化调用,更是为了消除所有的已检查异常。该拦截器只调用用来捕获可能抛出的任何已检查异常、并用SoftenedProtocolException将它们重新包装的目标方法。实际上,我们只重新包装消息,但要做到心中有数。SoftenedProtocolException扩展了RuntimeException,后者无疑是一个未检查异常。该操作的最终结果是应用程序开发人员不必处理任何已检查异常。如果他们真想处理异常,他们只需(非强制地)处理一个:SoftenedProtocolException。不错吧!
那么,如何才能让Spring完成这一切呢?答案就是要有正确的配置文件。我们现在就来看一看该配置文件,并了解其工作原理:
<!-- TARGET OBJECT -->
<bean id="protocolAdapter"
class="yourco.project.comm.CarrierPigeon">
<property name="isTired">
<value>true</value>
</property≷
<property name="canFlapWings">
<value>true</value>
</property≷
</bean≷
<!-- INTERCEPTOR -->
<bean id="sensorInterceptor"
class="yourco.project.springsupport.
SensorInvocationInterceptor"/>
<!--WIRE EVERYTHING UP, HAND BACK TO THE USER-->
<bean id="temperatureSensorOne"
class="org.springframework.aop.framework.
ProxyFactoryBean">
<property name="proxyInterfaces">
<value>
yourco.project.interfaces.SensorIfc
</value>
</property>
<property name="target">
<ref local="protocolAdapter"/>
</property>
<property name="interceptorNames">
<list>
<value>sensorInterceptor</value>
</list>
</property>
</bean>
我们逐节看这些代码时,可以看到它比我们前面看到的Spring配置文件要稍微复杂一些。“TARGET OBJECT”注释下面的第一节指定了要作为调用的目标对象的类。还记得拦截器曾将一个参数传入其中来表示目标对象吗?这就是其工作原理。基于该参数,Spring现在知道要将哪个对象传递进来。“INTERCEPTOR”下面的代码节是在目标方法之前调用的类。此时,如果目标类抛出已检查异常,要开始处理异常,并进行软化。Spring将它与目标类相关联。最后一节是将各种元素联系起来。用户要请求的bean位于bean键temperatureSensorOne下。ProxyFactoryBean将生成并返回一个代理类,它用来实现yourco.project.interfaces.SensorIfc接口。目标对象当然就是protocolAdapter
bean,它由yourco.project.comm.CarrierPigeon的实例支持。只要用户调用代理的方法就插入并调用的拦截器位于bean键sensorInterceptor下,并由yourco.project.springsupport.SensorInvocationInterceptor支持。请注意,可使用多个拦截器,因为该属性是一个列表,因此可以将许多拦截器对象关联到方法调用中,这是一个非常有用的理念。
运行该应用程序时,根据插入的CarrierPigeon值,我们可以看到一些有趣的行为。如果我们的CarrierPigeon没有使用过度并能执行,我们将看到这样的输出:
The sensor says the temp is:42
显然“信鸽”没问题而且状况很好,并计算出温度为42。如果由于改变CarrierPigeon Spring节中的值,而造成“信鸽”使用过度或不能执行,我们将得到如下所示的结果:
yourco.project.exceptions.comm.SoftenedProtocolException: protocol
error [comm failure]: I'm Tired!
at yourco.project.springsupport.SensorInvocationInterceptor.invoke
(SensorInvocationInterceptor.java:57)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed
(ReflectiveMethodInvocation.java:144)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke
(JdkDynamicAopProxy.java:174)
at .getSensorValue(Unknown Source)
at yourco.project.main.Main.main(Main.java:32)
在这种情况下,“信鸽”或者使用过度,或者不能执行,因此得到的结果是SoftProtocolException,并附带一条消息说明发生的情况:“I'm
tired!”够酷吧!
我希望人们能够开始了解Spring的强大及其多种功能。Spring框架悄然兴起,并成为开发人员的编程工具箱中的真正的“瑞士军刀”。Spring在实现让开发人员能够集中于应用程序的基本组成部分这一传说般的承诺方面做得不错,这也正是它得以大行其道的业务逻辑。Spring将您从J2EE中的一些错综复杂的方面中解放出来。Spring的强大在于它能够使大多数东西看起来就如同非常普通的Java对象一样,而不管它们的性质或来源如何。既然Spring自身肩负起创建、关联和配置的重担,那么开发人员要做的只是掌握使用对象而不是构造对象的方法。Spring就如同在杂货店购买的预煮的饭菜。您要做的只是决定想吃什么、把它带回家、加热,然后吃!
综合说明
Spring是一个非常健壮的轻量级框架,它极好地弥补了J2EE/EJB环境的不足。Spring真正伟大的一点在于它不走极端。您可以以一种非常简单的方式开始使用Spring(正如我所做的那样),只是建立常见的关联,作为定位器模式的一种实现。稍后,您将发现其惊人的功能,并且很快对它的要求会越来越多。
我所发现的一个惊人之处是Spring所提供的测试灵活性。现在人们对通过Junit进行单元测试日益重视,这增加了测试,而不是减少测试。J2EE容器的使用使测试极端复杂,以至于难以进行。这种困难局面源于业务逻辑和容器框架服务之间产生的耦合。借助于Spring的配置机制,使实现可以动态切换,这样就有助于将业务对象从容器中释放出来。我们已经看到,如果只想测试Web组件,将活动的EJB换成其代理或一个已清除的业务服务并不会造成太大影响。借助于JDBC或Hibernate数据访问对象,我们可以使用常见的简单JDBC、非XA的数据源连接来测试这些组件,并将它们无缝地换出,代之以健壮的基于JTA、JNDI的对应连接。结论是:如果代码易于测试,并因此测试得更多,那么质量必然会提高。
结束语
本文粗略地概括介绍了IoC,并详细介绍了Spring。Spring框架具有许多功能,其中许多功能在本文中只是点到为止。从基本的依赖注入到复杂的AOP操作,这些组成了Spring的强大功能,这是它的主要优点之一。能够根据问题需要使用或多或少的IoC功能是一个极具吸引力的理念,我认为,这在通常错综复杂的J2EE编程领域也是颇受欢迎的。现在看完了这篇文章,我衷心地希望它能对您有所帮助,并可以应用到您的工作中。欢迎随时提供反馈! |