?. 接口可插?/P>
q个例子表CZ个温度测量系l。几个传感器对象属于不同的类型,但都实现了ProtocolAdapterIfc接口Q因此在它们插入TemperatureSensor对象Ӟ它们是可互换的。在需要TemperatureSensorӞpȝ中的某个实体必须知道要生成ƈ与该传感器对象关联的ProtocolAdapterIfc的具体类型。在本例中,该传感器可基于命令行参数、数据库中的行或通过属性文件进行配|。本例还不以造成挑战或展CZ个复杂框Ӟ但它以阐明IoC基础?/P>
但是Q想象一下:在一个相当复杂的应用E序中这U情况屡屡发生,而您q希望能动态地——至要在外部——改变对象关联。假设有一个DummyProtocolAdapterQ它Lq回42q个|使用它来q行试。ؓ什么不提供一个单个的l一框架Q——让开发h员能够依靠该框架Q以一U一致的、外部配|的方式建立cM间的兌Qƈ且不引v工厂单元素类(factory singleton classe)的异常增加。这听v来可能没什么大不了Q但它要依赖于IoC的简单性?/P> 我们使用一个TemperatureSensorc,它与一个实现ProtocolAdapterIfc接口的类有关联。TemperatureSensor用该委托cL获得温度倹{如UML图所C,在实现ProtocolAdapterIfcq且随后可用于该兌的应用程序中有若q个cR我们将使用IoC框架Q在本例中是SpringQ来声明要用的ProtocolAdaperIfc的实现。Spring在q行时徏立关联。我们先来看XML代码Q它实例化TemperatureSensor对象q将一个ProtocolAdapterIfc实现与它兌h。该代码如下所C:
<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"/>
看了q些代码之后Q对于其目的应该非常清楚了。我们配|Spring来实例化TemperatureSensor对象Qƈ其与RS232Adapter相关联,作ؓ实现ProtocolAdapterIfc接口的类。若x变已l与TemperatureSensor兌的实玎ͼ惟一需要更改的是sensor bean标记中的class倹{只要实CProtocolAdapterIfc接口QTemperatureSensor׃再关心关联了什么?/P>
这应用于应用程序相当简单。我们必d接入Spring框架Q将它指向正的配置文gQ然后根据名U向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());
可以看出Q这些代码ƈ不是非常难。首先是启动Springq指定要使用的配|文件。接下来Ҏ名称QtempSensorQ引用Bean。Spring使用q样一U机ӞZsimpleSensor.xml文g的描q创对象q与其他对象兌。它用于注入依赖性——在本例中,通过它作ؓ一个参C递给sensorDelegate()Ҏ而实例化RS232Adapter对象q将其与TemperatureSensor对象兌?/P>
比较hQ用编E式Java完成q一d也不是很难。如下所C:
TemperatureSensor ts2 = new TemperatureSensor();
ts2.setSensorDelegate(new RS232Adapter());
UaM者或怼认ؓ实际上这是更好的Ҏ。代码行数少Qƈ且可L可能更强。确实如此,但这U方法的灉|性要得多?/P>
- 可以随意换入和换Z同层中不同对象的不同实现。例如,若Web层中的组仉要来自新业务对象的额外的功能Q您只需该业务对象与Web层对象相兌Q就像上面TemperatureSensor例子中的做法。它被“注入”到Web对象中以随时使用?
- 能够重新配置整个应用E序的结构,意味着可以L更改数据源。比如说Q或者ؓ不同的部|场景创Z同的配置文gQ或者ؓ试场景创徏更有用的、不同的配置文g。在试场景中可能会注入实现接口的模拟对象,而不注入真正的对象。稍后我们将介绍一个这L例子?
上面所q的例子可能是依赖注入的最单Ş式。利用相同的{略Q我们不仅能够关联不同的c,q能够在cM安装属性。诸如字W串、整数或点Ccȝ属性,只要hJavaBean样式的存取器Q就可以通过Spring配置文g它们注入类中。我们还可以通过构造函数来创徏对象和安装属性或bean引用。其语法只比通过属性进行设|稍E复杂一些?/P>
所有这一切都是利用一U灵zȝ声明性配|完成的。无需更改代码Q徏立依赖关联的所有艰难Q务都由Spring来完成?/P>
Spring--标准化的定位器模?/STRONG>
我一直将服务定位器模式视作良好的J2EE规范的主要组成部分。对于不熟悉q一术语的h来说Q可以这L解它Q我们一般认为典型的J2EE应用E序pq层l成。通常有Web层、服务层QEJB、JMS、WS、WLS控gQ以及数据库。一般来_完成某一h所需的“查䏀服务中都包含了一些方法。Service LocatorQ服务定位器Q模式认为,这些方法包装在某种隐藏了生成或查找l定服务的复杂性的工厂cM是一个好L。这减少了JNDI或只会造成Web层操作类混ؕ的其他服务品代码的增加。在Spring出现以前Q这通常是由l过考验证明可靠?tried-and-true)SingletoncL实现的。Singleton/Locator/Factory模式可以描绘为:
?. 定位器模式的序?/P>
q是Ҏ布在整个Web控制器代码中的增加的JNDI查找代码的一个巨大改q。它被y妙地隐藏在工厂内部的协作cM。我们可以用Spring来改q这一术语。此外,该解x案将适用于EJB、Web services、异步JMS调用Q甚臌有基于WLS控g的服务。由Spring实现的这U定位器模式的变体考虑了业务服务之间的一些抽象化和同质性。换句话_Web控制器的开发h员真的可以不考虑他们所使用的服务的U类Q一个类g“WLS控g”但是更通用的概c?/P>
IoC框架大大改进了这U模式的效用Q而且实际上废除了复杂而特D的singleton代码来实现它。通过借用上例中引入的概念Q我们实际上无需额外代码便能构徏一个非常强大且无处不在的Service Locator模式。ؓ此,在一开始有一个简单的要求Q即Web操作的开发h员应专门处理实现接口的那些事情。这基本上已l通过EJB~程实现Q但q不是说Web操作的开发h员处理的服务必须通过EJB来实现。它们可能只是普通Java对象或Web services。要Ҏ应当通过接口Q这样实现能够换入换出)来编写服务程序,q且q行旉|能够由Spring处理?/P>
Spring之所以非帔R合于Service Locator模式Q是因ؓ它或多或能够统一地处理不同类型的对象。通过许的规划和大量使用IoCQ我们多都能够以一U通用方式来处理大多数对象Q而不用管它们的特性(EJB、POJO{等Q如何,q且不会引vSingleton工厂cȝ增加。这使Web层编E变得更加轻村֒灉|?/P> 我们先来看一个关于这U模式如何应用于EJB的例子。我们都知道使用EJB可能是最复杂的方法,因ؓ要将一个活动的引用引入EJB要做很多工作。若使用SpringQ徏议用EJB接口扩展非特定于EJB的业务接口。这样做有两个目的:保持两个接口自动同步Q以及帮助保证业务服务对非EJB实现是可交换的,以便q行试或清?stubbing)。我们可以利用Spring固有的实用工h定位和创建EJB实例Q同时ؓ我们处理所有难以处理的工作。相应代码如下所C:
<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>
接下来可以检索beanq开始用它Q方法如下:
MyBizInterface myService = bf.getBean("myBizServiceRef");
q将q回Spring动态创建ƈ包装了底层目标(在本例中是一个本地EJB实例Q的一个对象。这U方法非常好Q因为它完全隐藏了我们在处理EJBq一事实。我们将与一个实现简单业务接口的代理对象交互。Spring已经Z“真正的”业务对象考虑周到地动态生成了该对象。所包装的对象当然就是Spring定位和检索引用所要获得的本地EJB。此外,您还会注意到Q这U代码Ş式与前面用于索tempSensor对象的代码完全相同?/P>
那么如果我们改变LQ想用普通Java对象来实C务组Ӟ或者可能在试中,我们想用一个返回“固?canned)”响应的已清?stubbed)对象来替换重量EJBQ该怎么做呢Q利用IoC和SpringQ通过更改Spring上下文文件就可轻而易丑֜实现q些目标。我们只需使用更常规一点的东西Q如我们在第一个Spring例子中所看到的)来替换EJB代理的连接即可:
<bean id="myBizServiceRef"
class="yourco.project.biz.MyStubbedBizService">
</bean>
h意,我只更改了Spring框架所q回的内容的l节Q没有更改bean id。最后的l果是业务对象的解决Ҏ未变Q它看上d以前完全一P
MyBizInterface myService =
bf.getBean("myBizServiceRef");
最大的区别昄是实现该业务接口的对象现在由一个普通Java对象QPOJOQ支持,q且只是该接口的一个已清除(stubbed)版本。这l单元测试或改变业务服务的特性带来了极大方便Q而对客户端代码的影响很小?/P>
使用Spring来标准化异常
Spring的一大A献是“模板化”代码块。这在纯JDBC~程中表现得最为明显。我们都曑ֆq具有下q功能的代码Q?/P>
- 创徏一个数据库q接Q可以的话从某个池创建?
- 构造一个查询字W串q提交?
- q代l果q将数据送到域对象中?
- 处理不同阶段出现的大量异常?
- 保记得~写finally代码块以关闭q接?
但是各处的这U代码往往都会或多或少地有点“样板化”。一般来说这是有害的Q不仅因Z需要的代码会增加,q因为有些东西可能会遗漏Q如非常重要的关闭连接,如果没有实现它,可能D数据资源池的泄漏?/P>
虽然我敢肯定我们都曾多次写过q类“样李쀝代码,但是SpringҎ和直接的JDBC实现对照来看Q其l果会有趣而又Ҏ鲜明。“传l”的JDBC实现可能如下Q?/P>
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();
}
对于该方法要做一些说明。首先,它是有效的!该代码绝对不会出CQ何错误。它会连接到数据库,q从‘SENSOR’表获取所需的数据。该Ҏ的基本问题源于缺乏抽象化。在大型应用E序中,必须反复剪切和粘贴这D代码,或者至会出现cM的其他情c较大的问题在于它依赖于~程人员d“该做的事”。我们都知道Q不数据库操作的结果是什么,都必ȝfinally语句来关闭该数据库。有时我们忘记做该做的事。我和所有h一h到内疚!
~写一个小框架来解册一问题会非常容易。我怿大家也都曾这样做q,但ؓ何不让Spring帮我们处理这一问题呢?我不敢保证我能想Z个更整洁、更优雅的解x案。我们来看Spring框架是如何处理这U样板JDBC场景的?/P>
Spring支持各种各样的JDBC、Hibernate、JDO和iBatis模板。模杉K用样板概念,q将它{换ؓ合法的编E术语。例如,下面的代码片断封装了上面列出的各个步骤:
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);
}
});
q段短的代码消除了JDBC~程的冗长,代表了前面提及的h的思\。请注意我们使用了Spring的IoC来查找该查询的数据源。Springq支持对已检查异怋用未查异常;因此许多已检查的JDBC异常会重新映到通常更有用而且更友好的未检查异常层ơ结构中。在Spring的上下文文g中配|该数据源类g下面代码Q?/P>
<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>
在本例中Q我们利用Apache commons工具配|了一个基本数据源。但q不是说我们只能使用它。我们可以改变配|,使用在JNDI中配|ƈ装蝲的数据源。Spring提供了一些实用工具和IoC功能以配|和q回存储在JNDI中的对象。例如,若想配置q用与JNDI上下文关联的数据源,则可以输入以下代码,替换先前的数据源配置Q?/P>
<bean id="myDataSource"
class="org.springframework.jndi.
JndiObjectFactoryBean">
<property name="tempSensorDS">
<value>ConnectionFactory</value>
</property>
</bean>
该代码突ZSpring所提供的关于表格的试灉|性。该代码可以在“容器内”运行(从JNDI查找数据源)Q经q细微的改动之后也可在“容器外”运行?/P>
虽然模板化机制仅适用于某些特定场合,但我们可以泛化这一概念Q将它应用于更广泛的场合。例如,一U将已检查异常{变ؓ未检查异常、此外还可能为异常处理提供一些中间或l一的异常处理策略的机制会很有帮助。我们还可以使用该机制将底层的基本异常“Y化”得更合乎h意。我们可以将PacketFrameParityFaultException软化为CommunicationsUnreliableException。这个重新映的较Y的异常表C情况可能ƈ不那么严重,重新h也是可以的?/P>
Spring已经具备了一U类g包装EJB调用Q在最后一节介l)的机Ӟ但遗憄是它q不具备M“通用”的东西Q至在异常软化意义上是q样的。但Spring的确有一个非常健壮的AOPQ面向方面编E)框架Q我们可以用它来Dq种行ؓ。下面是一个关于这UY化适用领域的(公认的)_ֿ设计的例子?/P>
我们再来看看本文前面已经开始探讨的一些概c在W一节中我们介绍了一个基于远E传感器的小应用E序。现在我们l探讨这个例子。我们将从一个简单的传感器接口开始介l,该接口代码如下:
public interface ProtocolAdapterIfc
{
public Integer getRemoteSensorValue()
throws CommChecksumFault,
CommConnectFailure,
CommPacketSequenceFault;
}
qƈ没有什么特别之处。显然实现该接口的Q何h都会获得一个远E值ƈ它q回l调用者。在此期间调用者可能要面对某些可怕的NQ该接口可能抛出的已查异常就是例证?/P>
接下来我们来看该接口的一个实现程序。实现ProtocolAdapter的类是CarrierPigeonQ其代码cM于:
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);
}
}
为简zv见,q里省略了属性的getter和setterҎ。当调用getRemoteSensorValue()ӞCarrierPigeonҎ检查它是否使用q度以及它还能否执行。如果它是使用q度q且不能执行Q我们将无法获得M|必须抛出CommConnectionFailureQ它是一个已查异常。到现在为止Q一直都q不错。但别高兴得太早了!我已l决定不让应用程序编Eh员应对“疲劳的信鸽”。可以将它包装在某种对象中,q在辑ֈ目的之前捕获异常Q但必须处理20U不同的传感器。此外,我更喜欢对这些东西进行适度透明地处理,随着对该pȝ了解的增多,或许q会更改异常处理E序中的逻辑。我惌的是一个兼容APIQ应用程序编Eh员能够用它Qƈ且随着旉的推U能够改变或增强。我x板化异常处理Qƈ让应用程序编Eh员能够处理Y的未查异常,而不是硬异常。Spring非常W合q种情况。下面是为此而用的{略的要点:
- 定义一个较软的消除已检查异常的最接口。这是应用~程人员用的接口?
- 使用Spring AOPl构Q开发一个客h调用和目标对象调用之间的拦截器,在本例中是“信鸽”?
- 使用Spring安装该拦截器q运行?
首先来看q个较Y的接口:
public interface SensorIfc
{
public Integer getSensorValue();
}
h意,在限定范围内可以重命名方法,使其更有意义。还可以消除已检查异常,像q里所做的一栗接下来Q也可能会更有趣的是我们惌让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接口q将该类插入SpringpȝQ稍后我们将q行讨论Q,我们通过MethodInvocation参数获得实际的目标方法。这样就能提取我们想要的真正对象。请CQ我们更改了被调用者看作SensorIfc的接口,该接口目标对象得以实现ProtocolAdapterIfc。我们这样做是ؓ了简化调用,更是Z消除所有的已检查异常。该拦截器只调用用来捕获可能抛出的Q何已查异常、ƈ用SoftenedProtocolException它们重新包装的目标Ҏ。实际上Q我们只重新包装消息Q但要做到心中有数。SoftenedProtocolException扩展了RuntimeExceptionQ后者无疑是一个未查异常。该操作的最l结果是应用E序开发h员不必处理Q何已查异常。如果他们真惛_理异常,他们只需Q非强制圎ͼ处理一个:SoftenedProtocolException。不错吧Q?/P>
那么Q如何才能让Spring完成q一切呢Q答案就是要有正的配置文g。我们现在就来看一看该配置文gQƈ了解其工作原理:
<!-- 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>
我们逐节看这些代码时Q可以看到它比我们前面看到的Spring配置文g要稍微复杂一些。“TARGET OBJECT”注释下面的W一节指定了要作用的目标对象的类。还记得拦截器曾一个参C入其中来表示目标对象吗?q就是其工作原理。基于该参数QSpring现在知道要将哪个对象传递进来。“INTERCEPTOR”下面的代码节是在目标方法之前调用的cR此Ӟ如果目标cL出已查异常,要开始处理异常,q进行Y化。Spring它与目标类相关联。最后一节是各U元素联pv来。用戯h的bean位于bean键temperatureSensorOne下。ProxyFactoryBean生成ƈq回一个代理类Q它用来实现yourco.project.interfaces.SensorIfc接口。目标对象当然就是protocolAdapter beanQ它由yourco.project.comm.CarrierPigeon的实例支持。只要用戯用代理的Ҏ插入ƈ调用的拦截器位于bean键sensorInterceptor下,q由yourco.project.springsupport.SensorInvocationInterceptor支持。请注意Q可使用多个拦截器,因ؓ该属性是一个列表,因此可以许多拦截器对象兌到方法调用中Q这是一个非常有用的理念?/P>
q行该应用程序时Q根据插入的CarrierPigeon|我们可以看到一些有的行ؓ。如果我们的CarrierPigeon没有使用q度q能执行Q我们将看到q样的输出:
The sensor says the temp isQ?2
昄“信鸽”没问题而且状况很好Qƈ计算出温度ؓ42。如果由于改变CarrierPigeon Spring节中的|而造成“信鸽”用过度或不能执行Q我们将得到如下所C的l果Q?/P>
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)
在这U情况下Q“信鸽”或者用过度,或者不能执行,因此得到的结果是SoftProtocolExceptionQƈ附带一条消息说明发生的情况Q“I'm tired!”够酷吧Q?/P>
我希望h们能够开始了解Spring的强大及其多U功能。Spring框架悄然兴vQƈ成ؓ开发h员的~程工具׃的真正的“瑞士军刀”。Spring在实现让开发h员能够集中于应用E序的基本组成部分这一传说般的承诺斚w做得不错Q这也正是它得以大行光的业务逻辑。Spring您从J2EE中的一些错l复杂的斚w中解攑և来。Spring的强大在于它能够使大多数东西看v来就如同非常普通的Java对象一P而不它们的性质或来源如何。既然Spring自n肩负起创建、关联和配置的重担,那么开发h员要做的只是掌握使用对象而不是构造对象的Ҏ。Spring如同在杂货店购买的预煮的饭菜。您要做的只是决定想吃什么、把它带回家、加热,然后吃!
l合说明
Spring是一个非常健壮的轻量U框Ӟ它极好地弥补了J2EE/EJB环境的不뀂Spring真正伟大的一点在于它不走极端。您可以以一U非常简单的方式开始用SpringQ正如我所做的那样Q,只是建立常见的关联,作ؓ定位器模式的一U实现。稍后,您将发现其惊人的功能Qƈ且很快对它的要求会越来越多?/P>
我所发现的一个惊Z处是Spring所提供的测试灵zL。现在h们对通过Junitq行单元试日益重视Q这增加了测试,而不是减测试。J2EE容器的用ɋ试极端复杂Q以至于难以q行。这U困隑ֱ面源于业务逻辑和容器框架服务之间生的耦合。借助于Spring的配|机Ӟ使实现可以动态切换,q样有助于业务对象从容器中释攑և来。我们已l看刎ͼ如果只想试WeblgQ将zd的EJB换成其代理或一个已清除的业务服务ƈ不会造成太大影响。借助于JDBC或Hibernate数据讉K对象Q我们可以用常见的单JDBC、非XA的数据源q接来测试这些组Ӟq将它们无缝地换出,代之以健壮的ZJTA、JNDI的对应连接。结论是Q如果代码易于测试,q因此测试得更多Q那么质量必然会提高?/P>
l束?/STRONG>
本文_略地概括介l了IoCQƈ详细介绍了Spring。Spring框架h许多功能Q其中许多功能在本文中只是点Cؓ止。从基本的依赖注入到复杂的AOP操作Q这些组成了Spring的强大功能,q是它的主要优点之一。能够根据问题需要用或多或的IoC功能是一个极具吸引力的理念,我认为,q在通常错综复杂的J2EE~程领域也是颇受Ƣ迎的。现在看完了q篇文章Q我衷心地希望它能对您有所帮助Qƈ可以应用到您的工作中。欢q随时提供反馈!
参考资?/STRONG>
原文出处
http://dev2dev.bea.com/pub/a/2005/07/better_j2eeing.html