您的位置:首页 > 其它

没有应用服务器的J2EE(2)

2006-03-24 18:25 666 查看
步骤4:增加Declarative 事务管理
Spring允许我们往任何可配置的对象中加入declarative 事务管理。假设我们希望确定bank 对象永远被一个有效的事务上下文所调用。为了达到这个目的,我们在实际对象的基础上再配置一个代理类。这个代理类和实际的对象拥有同一个接口,所以客户端能够正确地使用它。这个代理类能够被配置来包装每一个BankDAO 对象的方法以调用事务。配置文件如下。不要被这个庞大的XML文件所吓倒,大部分的内容能够以拷贝和粘贴的方式重用到你自己的项目中去。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org
/dtd/spring-beans.dtd">
<beans>
<!--
Use a JTA-aware DataSource
to access the DB transactionally
-->
<bean id="datasource"
class="com.atomikos.jdbc.nonxa.NonXADataSourceBean">
<property name="user">
<value>sa</value>
</property>
<property name="url">
<value>jdbc:hsqldb:SpringNonXADB</value>
</property>
<property name="driverClassName">
<value>org.hsqldb.jdbcDriver</value>
</property>
<property name="poolSize">
<value>1</value>
</property>
<property name="connectionTimeout">
<value>60</value>
</property>
</bean>
<!--
Construct a TransactionManager,
needed to configure Spring
-->
<bean id="jtaTransactionManager"
class="com.atomikos.icatch.jta.UserTransactionManager"/>
<!--
Also configure a UserTransaction,
needed to configure Spring
-->

<bean id="jtaUserTransaction"
class="com.atomikos.icatch.jta.UserTransactionImp"/>
<!--
Configure the Spring framework to use
JTA transactions from the JTA provider
-->
<bean id="springTransactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager">
<ref bean="jtaTransactionManager"/>
</property>
<property name="userTransaction">
<ref bean="jtaUserTransaction"/>
</property>
</bean>
<!-- Configure the bank to use our datasource -->
<bean id="bankTarget" class="jdbc.Bank">
<property name="dataSource">
<ref bean="datasource"/>
</property>
</bean>
<!--
Configure Spring to insert
JTA transaction logic for all methods
-->
<bean id="bank"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="springTransactionManager"/>
</property>
<property name="target">
<ref bean="bankTarget"/>
</property>
<property name="transactionAttributes">
<props>
<prop key="*">
PROPAGATION_REQUIRED, -Exception
</prop>
</props>
</property>
</bean>
</beans>
这个XML文件告诉Spring去配置如下的对象:
1.datasource 所需要的通过JDBC的连接
2.添加jtaTransactionManager 和jtaUserTransaction 对象来准备Spring的JTA事务配置
3.添加springTransactionManager 告诉Spring需要使用JTA事务
4.将BankDAO 在重新命名为bankTarget (下面将解释其中的原因)
5.bank对象被用来添加到所有的bankTarget的事务包装中来。我们配置bank对象来使用springTransactionManager,这意味着所有的事务是JTA事务。为每一个方法的事务设置PROPAGATION_REQUIRED,并且为所有的违例强迫使用会滚。
对于所有的对象,你可以很容易的拷贝和粘贴jtaTransactionManager、jtaUserTransaction和springTransactionManager 到其他的项目。仅仅与每个应用相关的对象为:datasource、bankTarget和bank。bank对象是很有趣的:它实际上是bankTarget对象的一个代理,它们拥有相同的接口。诀窍是这样的:当你的应用请求Spring配置并且返回一个叫bank的对象,Spring实际上返回的是一个代理(这看起来对于应用来说一样使用),并且这个代理为我们启动和关闭事务。通过这种方法,应用和bank对象都不需要知道JTA。图4显示了这个问题:


图4:带有declarative JTA事务的Spring架构
现在事情象下面所示的方向发展:
1.应用获取bank对象,启动了Spring的启动过程并且返回一个代理。对于应用来说,这个代理看起来和行为都跟bank对象一样
2.当调用一个bank的方法时,这种调用都是通过代理的
3.代理使用springTransactionManager 来产生一个新的事务
4.springTransactionManager 被配置为使用JTA,所以它的任何事务都委派到JTA
5.调用现在指向实际的bank,被命名为bankTarget
6.bankTarget 使用从Spring里取得的datasource
7.datasource 注册了一个事务
8.通过正常的JDBC来访问数据库
9.返回之后,代理终止了事务:如果在前面的过程没有违例,则终止机制为commit;否则,为rollback
10.事务管理和数据库协调commit(或者rollback)
有关测试这个过程的情况,我们可以重用BankTest 的外部的事务机制:由于PROPAGATION_REQUIRED的设置,代理将执行BankTest产生的事务上下文。

步骤5:对MessageDrivenBank编码
在这一步骤,我们加入JMS处理逻辑。为了达到这个目的,我们使用了JMS的MessageListener 接口。同样,我们也添加一个公有的setBank 方法来使的Spring的依赖注入工作。源代码如下:
package jms;

import jdbc.Bank;
import javax.jms.Message;
import javax.jms.MapMessage;
import javax.jms.MessageListener;

public class MessageDrivenBank
implements MessageListener
{
private Bank bank;

public void setBank ( Bank bank )
{
this.bank = bank;
}

//this method can be private
//since it is only needed within
//this class
private Bank getBank()
{
return this.bank;
}

public void onMessage ( Message msg )
{
try {
MapMessage m = ( MapMessage ) msg;
int account = m.getIntProperty ( "account" );
int amount = m.getIntProperty ( "amount" );
bank.withdraw ( account , amount );
System.out.println ( "Withdraw of " +
amount + " from account " + account );
}
catch ( Exception e ) {
e.printStackTrace();

//force rollback
throw new RuntimeException (
e.getMessage() );
}
}

}

步骤6:配置MessageDrivenBank
我们配置MessageDrivenBank 来监听QueueReceiverSessionPool事务(基于JTA)。这给与我们与EJB一样的保证(没有信息丢失和信息重复),但是这里我们使用了POJO代替。当一个MessageListener 对象加入到池里,池会确定是否收到了有关JTA/XA事务的消息。和基于JTA/XA的JDBC的数据源组合起来,我们能得到可信赖的消息。关于Spring的配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org
/dtd/spring-beans.dtd">

<!--
NOTE: no explicit transaction manager bean
is necessary
because the QueueReceiverSessionPool will
start transactions by itself.
-->
<beans>
<bean id="datasource"
class="com.atomikos.jdbc.nonxa.NonXADataSourceBean">
<property name="user">
<value>sa</value>
</property>
<property name="url">
<value>jdbc:hsqldb:SpringNonXADB</value>
</property>
<property name="driverClassName">
<value>org.hsqldb.jdbcDriver</value>
</property>
<property name="poolSize">
<value>1</value>
</property>
<property name="connectionTimeout">
<value>60</value>
</property>
</bean>
<bean id="xaFactory"
class="org.activemq.ActiveMQXAConnectionFactory">
<property name="brokerURL">
<value>tcp://localhost:61616</value>
</property>
</bean>
<bean id="queue"
class="org.activemq.message.ActiveMQQueue">
<property name="physicalName">
<value>BANK_QUEUE</value>
</property>
</bean>
<bean id="bank" class="jdbc.Bank">
<property name="dataSource">
<ref bean="datasource"/>
</property>
</bean>
<bean id="messageDrivenBank"
class="jms.MessageDrivenBank">
<property name="bank">
<ref bean="bank"/>
</property>
</bean>
<bean id="queueConnectionFactoryBean"
class="com.atomikos.jms.QueueConnectionFactoryBean">
<property name="resourceName">
<value>QUEUE_BROKER</value>
</property>
<property name="xaQueueConnectionFactory">
<ref bean="xaFactory"/>
</property>
</bean>
<bean id="queueReceiverSessionPool"
class="com.atomikos.jms.QueueReceiverSessionPool"
init-method="start">

<property name="queueConnectionFactoryBean">
<ref bean="queueConnectionFactoryBean"/>
</property>
<property name="transactionTimeout">
<value>120</value>
</property>
<!--
default license allows only limited
concurrency so keep pool small
-->
<property name="poolSize">
<value>1</value>
</property>
<property name="queue">
<ref bean="queue"/>
</property>
<property name="messageListener">
<ref bean="messageDrivenBank"/>
</property>
</bean>
</beans>
因为本文所用的JMS要求易于安装,这里我们将使用ActiveMQ 。如果你使用另外的一个JMS实现,那么这个实现也需要满足本文这个部分所列出的一些技术要求。就像增加datasource对象和bank一样,下面所定义的一些对象也需要被添加进来:
。xaFactory:用来建立JMS连接的连接工厂
。queue:表示我们所要用到的JMS对列,用来配置ActiveMQ 所需要的线路
。queueConnectionFactoryBean:基于JTA的JMS连接
。queueReceiverSessionPool:基于JTA的消息池。注意:我们也给定一个初始化方法(例如:start)来被调用。这也是Spring的一个特性。这个start方法在session池里被定义,并且在Spring配置文件里被XML属性所引用
。messageDrivenBank:作用是处理消息
你可能会问自己,事务管理在哪里执行的啊?实际上,前面部分所添加的对象又一次不见了。为什么?因为我们现在使用QueueReceiverSessionPool来从JMS里获取消息,并且这些对象在每一个receive的事务中被启动。我们可以丝毫不改动JTA配置,而仅仅增加JMS元素。但这会使得XML文件变得有的长。session池会确定在步骤5里所增加的事务管理任务。它运行起来有点像代理方法一样,仅仅这一个类需要JMS MessageListener来增加事务。通过这个配置,在每一次产生消息之前就会开始一个新的事务。无论onMessage实现什么时候正常的返回,这个事务将会提交。如果是一个RuntimeException,那么事务将会被会滚。这个架构显示在图5(一些JMS对象被忽略掉)



图5:消息驱动的应用在Spring里的架构
现在这个架构运行如下:
1。应用获取bank对象,并且在需要的情况下初始化数据库的表
2。应用获取queueReceiverSessionPool,因而触发了一个start方法的调用,开始监听输入的消息
3。queueReceiverSessionPool从消息队列中监测到一个新的消息
4。queueReceiverSessionPool开始一个新的事务并且注册
5。queueReceiverSessionPool调用已经注册了的MessageListener(在我们的例子中是messageDrivenBank)
6。触发对于bank的调用
7。bank使用datasource访问数据库
8。bank注册事务
9。数据库通过JDBC被访问
10。当整个过程被处理完以后,queueReceiverSessionPool结束事务,除非是抛出RuntimeException,期望的结果是commit
11。在消息队列里,事务管理器初始化两个状态的commit
12。在数据库里,事务管理器初始化两个状态的commit


步骤7:给应用编码
既然我们不使用容器,我们仅仅提供一个Java应用来启动这个银行系统。我们的Java应用很简单:它仅仅用来取得配置对象(在读取XML文件的期间和Spring绑定)。这个应用能够运行在所有的JDK(Java Development Kit)上,并且不需要启动一个应用服务器。
package jms;

import java.io.FileInputStream;
import java.io.InputStream;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import com.atomikos.jms.QueueReceiverSessionPool;
import jdbc.Bank;

public class StartBank
{
public static void main ( String[] args )
throws Exception
{
//open bean XML file
InputStream is =
new FileInputStream(args[0]);

//the factory is Spring's entry point
//for retrieving the configured
//objects from the XML file
XmlBeanFactory factory =
new XmlBeanFactory(is);

//retrieve the bank to initialize
//alternatively, this could be done
//in the XML configuration too
Bank bank =
( Bank ) factory.getBean ( "bank" );

//initialize the bank if needed
bank.checkTables();

//retrieve the pool;
//this will also start the pool
//as specified in the beans XML file
//by the init-method attribute!

QueueReceiverSessionPool pool =
( QueueReceiverSessionPool )
factory.getBean (
"queueReceiverSessionPool" );

//Alternatively, start pool here
//(if not done in XML)
//pool.start();

System.out.println (
"Bank is listening for messages..." );

}
}
看,那就是啊。难道不是J2EE这些天来变得比以前简单了吗?


一般性的讨论
在剩下的部分,我们来看一看其他一些对于J2EE应用很重要的概念。同时,我们也将看到,对于这些应用来说,应用服务器并不是必需的。
群集和可测性
一个鲁棒性好的企业级应用需要群集能力来大批量的启动和关闭。在消息驱动的应用的案例中,这很简单:我们自动的取得JMS应用固有的可测性特性。如果我们需要更强的处理能力,只需要增加更多的过程连接到同一个JMS服务器上。一个有用的服务器性能测试手段是测量队列中有用的消息数。在其他的像基于WEB的架构中(如下),我们可以很容易的使用WEB环境的群集能力。
方法级的安全
宠爱EJB的一个典型的证据是能够增加方法级的安全。虽然没有在这个应用的演示,但是在Spring中增加方法级的安全应该是可能的,就像我们增加方法级的事务一样。
推广到非消息驱动的应用
我们所用的平台能够很容易的集成到所有的J2EE WEB服务器上,不需要修改源代码(main application class除外)。作为一个选择,后台的处理也可以直接调用JMS,这样能够使得WEB服务器能够响应潜在的后台处理,并且保持和后台处理的独立。不管怎么说,使用一个EJB容器可以不需管容器管理的事务,或者做容器管理的安全。
容器管理的持久层怎样呢
现有的已经得到证明了的技术,如JDO或Hibernate对于一个应用服务器来说不是必需的。实际上,这些工具已经主导了可管理的持久层市场。


结论
没有应用服务器,J2EE会变得更加简单;而且现在这也是可能的。有人说,有一些应用在没有应用服务器的情况下,无法实现。例如,如果你需要一个普通的JCA(Java Connectivity API),那么我们在这里提到的平台是不够用的。然而,这种情况很有可能改变,因为不使用应用服务器来开发、测试和发布一个应用所获得的利益是巨大的。越来越多的人相信:未来的J2EE应用将是一个模块化的“按需获取”的架构,而不是今天的一整块的基于应用-服务的架构。根据这个假设,J2EE开发人员将从应用服务器和EJB强加的一些限制中解放出来。

资源

The complete source code for this article

Guy Pardon's presentation on Transactions in Spring published at TheServerSide

More information on Atomikos Transactions and message-driven functionality without EJB

The home page of Spring

More information on JUnit

FirstSQL is an easy-to-install, XA-compliant DBMS

More information on HSQLDB

More information on ActiveMQ


关于作者
Guy PardonAtomikos公司的首席架构师。在公司里,他领导了开发既有传统的也有现在的(面向WEB服务)的事务技术

原文链接
http://www.onjava.com/pub/a/onjava/2006/02/08/j2ee-without-application-server.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: