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

步骤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 强加的一些限制中解放出来。
 
资源
 
关于作者
Guy Pardon Atomikos 公司的首席架构师。在公司里,他领导了开发既有传统的也有现在的(面向 WEB 服务)的事务技术
 
原文链接
http://www.onjava.com/pub/a/onjava/2006/02/08/j2ee-without-application-server.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值