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

本文介绍了一种不需要应用服务器的J2EE应用开发方式,利用Spring框架简化了EJB复杂的编程模型,实现了消息驱动的银行系统示例。
                                                    没有应用服务器的J2EE
                                                                                作者: Guy Pardon
 
 
尽管由于在J2EE平台(应用服务器)和它的规范的编程模型(企业JavaBeans,或者EJB)这两个方面的复杂性而名声狼藉,但基于J2EE的应用在企业级市场还是越来越成功。这要感谢一些现代化的思想,像依赖注入(IOC)和面向方面的编程技术(AOP),以及实现这些思想的轻量级容器,如Spring开发框架,J2EE的编程模型变得相当简单,并且相当优雅。不过,即便是使用这些工具,应用服务器仍然是复杂度和资源开销的一个主要来源。本文的目的是通过展现一个消除昂贵的运行期平台——应用服务器的方法,来展示一个更加简单的J2EE应用。特别的,本文展示了很多应用不再需要运行一个应用服务器。结果,J2EE变成了如下的样子:
容易编成,不需要EJB代码
简单,没有EJB类或接口需要被继承
易于测试,你的应用和测试能够在集成的开发环境中运行
更加少的资源需求,你仅仅需要你的对象,而不需要应用服务器和它的对象
易于安装,既没有应用服务器需要安装,也没有额外的XML文件需要加入
易于维护,既然所有的过程都简单,那么维护起来也就更加简单
 
对于许多项目和开发团队来说,就像一个被长时间的掌声中断了的表演一样,J2EE不必要的复杂度也使得项目长时间不能结束。到了今天,通过本文所列的一些方法,这种复杂度常常能够被避免。另外,保留甚至提高一些典型的服务,如事务和安全是可能的。J2EE编程从来没有像现在这么有趣。
 
示例:消息驱动的银行
为了展示我的观点,我们将开发和安装一个完整的示例应用:一个消息驱动的银行系统。我们将完成这样一个系统而不需要EJB或者应用服务器,而保留相同的事务保证书,并且(感谢Spring)使用一个基于简单的旧JAVA对象(POJO)的一个改进的编程模型。在文章后面的部分,我们将把这样一个方案从消息驱动的银行系统扩展到其他基于WEB的应用。图1展示了我们的示例应用的结构。
图1:消息驱动的银行系统的架构
 
我们假定,我们将通过JAVA消息服务(JMS)队列来处理流入的银行订单。订单的处理包括通过JDBC来更新帐户数据。为了避免信息的丢失和重复,我们使用JAVA事务API(JTA)和JTA/XA事务来同步更新。咨询一个消息和更新数据库是一个原子事务。如果想了解为什么我们使用JTA/XA的原因信息,请查看资源部分的内容。
 
对应用编码
应用由两个简单的JAVA类组成:Bank(一个数据访问对象,或DAO)和MessageDrivenBank,如图2所示:
图2:消息驱动的银行系统的类关系
 
Bank类是一个数据访问对象,它封装了数据库访问。MessageDrivenBank 类是消息驱动的正面,用来委派DAO。不像一个典型的J2EE应用,这里没有EJB类被包括进来。
Bank类的源代码是与JDBC相关的代码,如下所示。需要指出的是,由于众所周知的原因,清除资源和违例处理我们所做的非常简单。
package jdbc;
 
import javax.sql.*;
 
import java.sql.*;
 
public class Bank
{
 
 private DataSource dataSource;
 
   
 public Bank() {}
 
 public void setDataSource ( DataSource dataSource )
 {
    this.dataSource = dataSource;
 }
 
 private DataSource getDataSource()
 {
    return this.dataSource;
 }
 
 
 private Connection getConnection()
 throws SQLException
 {
    Connection ret = null;
    if ( getDataSource() != null ) {
        ret = getDataSource().
              getConnection();
    }
    return ret;
 }
 
 private void closeConnection ( Connection c )
 throws SQLException
 {
    if ( c != null ) c.close();
 }
   
 public void checkTables()
 throws SQLException
 {
       
    Connection conn = null;
    try {
      conn = getConnection();
      Statement s = conn.createStatement();
      try {
        s.executeQuery (
        "select * from Accounts" );
      }
      catch ( SQLException ex ) {
        //table not there => create it
        s.executeUpdate (
        "create table Accounts ( " +
        "account VARCHAR ( 20 ), " +
        "owner VARCHAR(300), " +
        "balance DECIMAL (19,0) )" );
        for ( int i = 0; i < 100 ; i++ ){
          s.executeUpdate (
          "insert into Accounts values ( " +
          "'account"+i +"' , 'owner"+i +"', 10000 )"
          );
        }
      }
      s.close();
      }
      finally {
        closeConnection ( conn );
 
      }
 
      //That concludes setup
 }
 
   
 //
 //Business methods are below
 //
 
 public long getBalance ( int account )
 throws SQLException
 {
       
    long res = -1;
    Connection conn = null;
 
    try {
      conn = getConnection();
      Statement s = conn.createStatement();
     
      String query =
      "select balance from Accounts where account='"+
      "account" + account +"'";
     
      ResultSet rs = s.executeQuery ( query );
      if ( rs == null || !rs.next() )
        throw new SQLException (
        "Account not found: " + account );
      res = rs.getLong ( 1 );
      s.close();
    }
    finally {
        closeConnection ( conn );
    }
    return res;
       
 }
 
 public void withdraw ( int account , int amount )
 throws Exception
 {
    Connection conn = null;
 
    try {
      conn = getConnection();
      Statement s = conn.createStatement();
 
      String sql =
      "update Accounts set balance = balance - "+
      amount + " where account ='account"+
      account+"'";
     
      s.executeUpdate ( sql );
      s.close();
   
    }
    finally {
        closeConnection ( conn );
 
    }
 }
        
}
注意:上面的代码没有有关EJB依赖,也没有与应用服务器细节的任何关系。实际上,这只是一个普通的JAVA类,可以运行在任何的JAVA标准版(J2SE)环境。
同时你也需要注意到:我们使用的是JDBC的普通的DataSource 接口。这意味着我们的代码不依赖于某个具体的JDBC Driver提供商。你可能会想知道这些代码是如何依赖你的数据库管理系统(DBMS)的提供商的JDBC实现的。这是Spring所做的工作。这种技术叫依赖注入:当在我们的应用启动的时调用setDataSource 方法的时候,Spring就会提供datasource 对象。下面的章节将会涉及到更多的Spring的知识。如果我们使用应用服务器的话,那么我们将使用JAVA命名和目录服务接口(JNDI)来代替。
除了直接访问JDBC,我们也可以使用Hibernate 或JAVA数据对象(JDO)来做我们的持久层。同样,这些技术也与EJB无关。
我们将使用Spring来配置我们的应用。严格来说,Spring也不是必需的,但是使用它的一个优势是我们可以很容易的添加一些服务,如事务和安全,到我们的JAVA对象中来。这点跟应用服务器所允许我们使用的服务有些相似,但仅仅是在我们示例这种情况下应用服务器才会使得我们的编码简单一些。Spring也允许我们解耦我们的实际类和JDBC Driver实现:Spring将会做这些事,配置JDBC驱动(基于我们的XML配置数据)和实现它到BankDAO 对象(依赖注入原则)。这些使得我们的代码干净和专注。下面所显示的步骤就是一个Spring的配置文件:
"http://www.springframework.org
/dtd/spring-beans.dtd">
 
 
class="com.atomikos.jdbc.nonxa.NonXADataSourceBean">
 
   
        sa
   
   
        jdbc:hsqldb:SpringNonXADB
       
   
   
        org.hsqldb.jdbcDriver
   
   
        1
   
   
        60
   
 
       
           
       
 
这个XML文件包含两个对象的配置设置:用来访问数据库的DataSource 对象和用来访问数据库的Bank 类。下面所示的是Spring所要做一些基本的任务:
。产生应用所需要的对象(beans),例如bank 和datasource。这些对象所属的类由XML文件来定义,在我们的例子中,这些对象都有一个公有的没有参数的构造器(Spring也允许带参数的构造器,但这需要一个稍微不同的XML语法)。这些对象都已经被命名(通过XML文件的id属性),所以我们能够在后面引用它们。这个id也需要我们的应用能够获得它所需要的配置对象。
。初始化这些对象是通过在XML文件里那些值的属性获得的。这些XML文件里的属性名应该和被引用的类的SetXXX方法相对应。
。链接其他对象。一个属性能够被引用到另外一个对象上(如在我们的例子中的datasource),该引用是通过id标识的。
需要注意到:在我们的下一个步骤中,我们将配置一个基于JTA的数据源(在我们的应用中使用支持Atomikos 事务、具有企业级能力的兼容J2SE平台的JTA产品)。对于一个简单的示例,常用的DBMS 是HypersonicSQLDB,它不要求一些特别的安装步骤。就像Spring和JTA一样,它运行在.jar文件上。如果要求更好的可靠性,强烈建议使用XA-capable DBMS 和JDBC drivers,如果没有XA,你的应用将在崩溃或重启的时候不能恢复。请你查看 资源部分以获得更多的关于事务和XA方面的信息以及它们的使用条件。作为一个实际的练习,你可以将HypersonicSQLDB 换为FirstSQL,FirstSQL是一个易于安装的、兼容XA的数据库系统。或者,其他企业级的、兼容XA的数据库系统均可。
 
步骤3:测试BankDAO
现在让我们来测试我们的代码,(极限编程先写测试,这里为了讲述的方便,到现在才写测试)下面将是一个简单的单元测试。这个测试代码将运行在你的标准的IDE上,与应用服务器无关。这个单元测试被当作一个它自己最小的应用而设计:它通过Spring获得一个Bank 对象来测试(是通过setUp 方法来做到的)。注意:测试使用外部的事务:在每一个测试开始之前事务就开始了,并且在每一个测试之后强制会滚。这是一个简单的方法使得测试对数据库的影响降到最低。
package jdbc;
import com.atomikos.icatch.jta.UserTransactionImp;
import junit.framework.TestCase;
import java.io.FileInputStream;
import java.io.InputStream;
import org.springframework.beans.factory.xml.XmlBeanFactory;
 
public class BankTest extends TestCase
{
 
    private UserTransactionImp utx;
 
    private Bank bank;
 
    public BankTest ( String name )
    {
        super ( name );
        utx = new UserTransactionImp();
       
 
    }
 
    protected void setUp()
        throws Exception
    {
        //start a new transaction
        //so we can rollback the
        //effects of each test
        //in teardown!
        utx.begin();
       
        //open bean XML file
        InputStream is =
            new FileInputStream("config.xml");
 
        //the factory is Spring's entry point
        //for retrieving the configured
        //objects from the XML file
 
        XmlBeanFactory factory =
            new XmlBeanFactory(is);
 
        bank = ( Bank ) factory.getBean ( "bank" );
        bank.checkTables();
    }
 
    protected void tearDown()
        throws Exception
    {
        //rollback all DBMS effects
        //of testing
        utx.rollback();
    }
 
    public void testBank()
    throws Exception
    {
        int accNo = 10;
        long initialBalance = bank.getBalance ( accNo );
        bank.withdraw ( accNo , 100 );
        long newBalance = bank.getBalance ( accNo );
        if ( ( initialBalance - newBalance ) != 100 )
            fail ( "Wrong balance after withdraw: " +
                   newBalance );
    }
   
}
我们需要JTA来确保JMS和JDBC都在原子状态下运行。一般情况下,你需要考虑JTA/XA,当你处在多个连接的情况下,在我们的例子中,有JMS和JDBC。Spring本身不提供JTA事务,它需要一个JTA实现,而且经常委派到应用服务器中去做这件事。然而,我们也可以使用一个嵌入式的运行在J2SE平台的JTA实现。
下图是我们的结构,白框是我们应用的代码:
                      图3:测试结构
 
你将会看到,当我们运行测试的时候,将发生如下:
1.BankTest 启动了一个新事物。
2.测试类对象从Spring框架获得一个bank 对象,这一步触发Spring的创建和初始化过程。
3.测试类对象调用bank 的方法。
4.bank 引用了datasource,它是从Spring中通过setDataSource 方法获得的。
5.datasource 是基于JTA的,通过与JTA实现交互而注册到当前的事务中来。
6.JDBC statement和accounts 数据库交互。
7.当方法运行完,测试对象调用事务的会滚。
       8.JTA搜索datasource ,并且通知会滚。


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值