盡管J2EE平臺(tái)(應(yīng)用程序服務(wù)器)及其編程模型(企業(yè)JAVA組件,簡稱EJB)擁有的眾所周知的復(fù)雜性,但是基于J2EE的應(yīng)用程序仍然在企業(yè)領(lǐng)域里變得非常成功.我們要感謝應(yīng)用于輕量級(jí)容器的控制反轉(zhuǎn)(IoC)和面向方面編程(AOP),比如Spring框架. 我們能夠更簡單地設(shè)計(jì)更大型的編程模型。然而,即使有了這些工具,應(yīng)用服務(wù)器仍然是復(fù)雜度和消耗的一個(gè)重要瓶頸。這篇文章提供了一個(gè)對(duì)J2EE的簡化,展示了如何消除應(yīng)用服務(wù)器的消耗和限制。特別地,這篇文章提到了:許多應(yīng)用程序?qū)嶋H上并不需要運(yùn)行應(yīng)用服務(wù)器。這樣,J2EE應(yīng)用組件將會(huì)變得:
· 開發(fā)更容易:不再需要EJB運(yùn)行代碼;
· 更簡單: 繼承不需要EJB類或接口;
· 測(cè)試更容易:你的應(yīng)用程序及測(cè)試能在你的開發(fā)環(huán)境(IDE)中直接運(yùn)行;
· 更少的資源消耗:你只需要你的對(duì)象,不需要應(yīng)用服務(wù)器,更不需要應(yīng)用服務(wù)器的對(duì)象;
· 安裝更容易:沒有運(yùn)行應(yīng)用服務(wù)專門的安裝軟件, 沒有加載額外的XML文件;
· 維護(hù)更容易:所有的過程都更簡單,因此維護(hù)也更容易。
J2EE不必要的復(fù)雜度已經(jīng)成為一個(gè)阻礙。今天,這種復(fù)雜度能夠通過在這篇文章中提到的方法來避免。另外,程序還能夠保留事務(wù)和安全這些典型的服務(wù)。J2EE程序從來沒有比這更有趣過。
版權(quán)聲明:任何獲得Matrix授權(quán)的網(wǎng)站,轉(zhuǎn)載時(shí)請(qǐng)務(wù)必保留以下作者信息和鏈接作者:Guy Pardon;
chmei83(作者的blog:
http://blog.matrix.org.cn/page/chmei83)
原文:
http://www.onjava.com/pub/a/onjava/2006/02/08/j2ee-without-application-server.html譯文:
http://www.matrix.org.cn/resource/article/44/44250_J2ee+Application+Server.html關(guān)鍵字:J2ee;Application;Server
例子:消息驅(qū)動(dòng)Bank為了闡述我們的觀點(diǎn),我們將開發(fā)和安裝一個(gè)完整的樣板應(yīng)用程序:一個(gè)消息驅(qū)動(dòng)的銀行系統(tǒng). 通過(幸虧有Spring)改進(jìn)的基于POJOs的編程模型和保留相同的事務(wù),我們可以不需要EJB或者一個(gè)應(yīng)用服務(wù)器來實(shí)現(xiàn)這個(gè)系統(tǒng)。在下一個(gè)部分,我們將從消息驅(qū)動(dòng)架構(gòu)產(chǎn)生到另一個(gè)架構(gòu).就像基于WEB的架構(gòu)一樣.圖1展示我們的樣本應(yīng)用程序的架構(gòu).
Figure 1. Architecture of the message-driven bank
在我們的例子中,我們將處理來自Java消息服務(wù)隊(duì)列的銀行定單.一張定單的處理包括通過JDBC來更新當(dāng)前帳戶的數(shù)據(jù)庫.為了避免信息的丟失和重復(fù),我們將使用JTA和JTA/XA事務(wù)來配合更新:處理信息和更新數(shù)據(jù)庫將發(fā)生在一個(gè)原子事務(wù)里.資源部分可得到JTA/XA的更多信息.
編寫應(yīng)用程序代碼該應(yīng)用程序?qū)⒂蓛蓚€(gè)JAVA類組成: Bank(一個(gè)DAO)和MessageDrivenBank.如圖2.
Figure 2. Classes for the message-driven bank
Bank是一個(gè)數(shù)據(jù)訪問對(duì)象,這個(gè)對(duì)象封裝數(shù)據(jù)庫訪問。MessageDrivenBank是一個(gè)消息驅(qū)動(dòng)façade并且是DAO的委托.與典型的J2EE方法不同,這個(gè)應(yīng)用程序不包括EJB類.
第一步:編寫B(tài)ank DAO如下, 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 ( " +
"‘a(chǎn)ccount"+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 =‘a(chǎn)ccount"+
account+"‘";
s.executeUpdate ( sql );
s.close();
}
finally {
closeConnection ( conn );
}
}
}
注意:代碼并沒有依賴EJB或任何專門的應(yīng)用程序服務(wù)器.實(shí)際上,這是一個(gè)純JAVA代碼,這個(gè)JAVA代碼是能在任何J2SE環(huán)境下運(yùn)行的.
你同時(shí)應(yīng)注意:我們使用了來自JDBC的DataSource接口.這意味著我們的類是獨(dú)立于目前JDBC供應(yīng)商提供的類. 你可能會(huì)疑惑,這怎么能與特定的數(shù)據(jù)管理系統(tǒng)(DBMS)提供商的JDBC實(shí)現(xiàn)緊密結(jié)合呢? 這里就是Spring框架幫你實(shí)現(xiàn)的. 這個(gè)技術(shù)被稱為依賴注入:在我們的應(yīng)用程序的啟動(dòng)期間,通過調(diào)用setDataSource方法,Spring為我們提供了相應(yīng)的datasource對(duì)象.在后面幾部分我們會(huì)更多地提到Spring.如果我們?cè)谝郧笆褂脩?yīng)用程序服務(wù)器,我們將不得不借助于JAVA命名綁定接口(JNDI)查詢.
除了直接使用JDBC,我們也可以使用Hibernate或者一個(gè)JDO工具來實(shí)現(xiàn)我們的持久層.這同樣不需要任何的EJB代碼.
第二步:配置BankDAO我們會(huì)將便用Spring框架來配置我們的應(yīng)用程序.Spring不是必需的,但是使用Spring的好處是我們將可以簡單的添加服務(wù),如:我們JAVA對(duì)象的事務(wù)和安全.這類似于應(yīng)用服務(wù)器為EJB提供的東西,只是在我們的例子中Spring將變得更容易.
Spring也允許我們把我們的類從目前的JDBC驅(qū)動(dòng)實(shí)現(xiàn)中分離出來:Spring能夠配置Driver(基于我們的XML配置數(shù)據(jù))并把它提供給BankDAO對(duì)象(依賴注入原理).這樣可以保持我們的JAVA代碼的清淅和集中.這步的Spring配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<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="bank" class="jdbc.Bank">
<property name="dataSource">
<ref bean="datasource"/>
</property>
</bean>
</beans>
這個(gè)XML文件包括兩個(gè)對(duì)象的配置:訪問數(shù)據(jù)庫的DataSource和使用這個(gè)DataSource的Bank對(duì)象.下面是由Spring維護(hù)的一些基本任務(wù).
· 創(chuàng)建應(yīng)用程序(例: Bank和DataSource)需要的對(duì)象(“beans”).在XML文件中給出了這些對(duì)象的類名,并且在我們的例子中,這些對(duì)象需要有一個(gè)公共的無參數(shù)constructor (Spring也允許參數(shù),但是配置語法上有所不同).這些對(duì)象都被命名(XML中的id屬性),所以我們后面能夠引用這些對(duì)象. id也允許我們的應(yīng)用程序找回它需要的已配置對(duì)象.
· 這些對(duì)象的初始化是通過在XML文件中的properties的值實(shí)現(xiàn). 在XML文件中這些properties名 應(yīng)與對(duì)應(yīng)的類中的setXXX方法相對(duì)應(yīng).
· 將對(duì)象連接在一起 :一個(gè)property可能是另一個(gè)對(duì)象(例如:在我們例子中的數(shù)據(jù)源)的引用,引用可以通過id創(chuàng)建.
注意:在我們下一步中, 我們將選擇配置一個(gè)JTA-enabled的數(shù)據(jù)源(由Atomikos Transactions提供,可用于企業(yè)和J2SE的JTA產(chǎn)品,我們將應(yīng)用于我們的應(yīng)用程序). 簡單起見,我們將使用HypersonicSQLDB,這個(gè)DBMS不需要專門的安裝步驟—它是在.jar文件里,就像JTA和Spring.
但是,考慮到漸增的可靠性需求,強(qiáng)列推薦你使用XA-capable的DBMS和JDBC驅(qū)動(dòng).沒有XA的支持, 在crash或重啟之后你的應(yīng)用程序?qū)⒉荒芑謴?fù)原有數(shù)據(jù). 資源部分有鏈接到關(guān)于事務(wù)和XA的信息和一些例子.
作為一個(gè)練習(xí),你可以試試從HypersonicSQLDB轉(zhuǎn)換到FirstSQL,一個(gè)易安裝XA-compliant的DBMS.換句話說,任何其他為企業(yè)準(zhǔn)備的和XA-capable的DBMS也會(huì)做得很好.
第三步:測(cè)試BankDAO讓我們來測(cè)試我們的代碼,(使用極限編程的程序員會(huì)首先寫測(cè)試,但因開始不是很清淅,所以我們直到現(xiàn)在才開始寫測(cè)試.)下面是一個(gè)簡單的單元測(cè)試.這個(gè)測(cè)試可在你的的應(yīng)用程序里運(yùn)行:它通過Spring獲得一個(gè)BANK對(duì)象來進(jìn)行測(cè)試(這在setUp方法中實(shí)現(xiàn)).注意:這個(gè)測(cè)試使用清楚的事務(wù)劃分:每一個(gè)測(cè)試開始之前開始一個(gè)事務(wù),每個(gè)測(cè)試結(jié)束時(shí)強(qiáng)制進(jìn)行事務(wù)回滾.這是通過手工的方式來減少測(cè)試對(duì)數(shù)據(jù)庫數(shù)據(jù)的影響.
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事務(wù)來確保JMS和JDBC都是原子操作.一般來說,當(dāng)經(jīng)常都是兩個(gè)或多個(gè)連接的時(shí)候,你應(yīng)考慮一下JTA/XA。例如,在我們例子中的JMS和JDBC. Spring本身不提供JTA事務(wù);它需要一個(gè)JTA實(shí)現(xiàn)或者委派一個(gè)應(yīng)用服務(wù)器來處理這個(gè)事務(wù).在這里,我們使用了一個(gè)JTA實(shí)現(xiàn),這個(gè)實(shí)現(xiàn)可以在任何J2SE平臺(tái)上工作.
最終架構(gòu)如下面圖3.白色方框代表我們的應(yīng)用程序代碼.
Figure 3. Architecture for the test
如你所看到的,當(dāng)我們執(zhí)行我們的測(cè)試,將會(huì)發(fā)生下面的情況:
1. BankTest開始一個(gè)新事務(wù).
2. 然后,這個(gè)test在Spring運(yùn)行期間獲得一個(gè)BANK對(duì)象.這步觸發(fā)Sping的創(chuàng)建和初始化過程.
3. 這個(gè)test調(diào)用BANK的方法.
4. BANK調(diào)用datasource對(duì)象,通過它自己的setDataSource 方法從Spring 獲取這個(gè)對(duì)像.
5. 這個(gè)數(shù)據(jù)源是JTA-enabled,并且與JTA實(shí)現(xiàn)交互來注冊(cè)當(dāng)前事務(wù).
6. JDBC statements和帳戶數(shù)據(jù)庫交互.
7. 當(dāng)方法返回時(shí), test調(diào)用事務(wù)回滾.
8. JTA記得住datasource對(duì)象,會(huì)命令它進(jìn)行回滾.
第四步:添加聲明式事務(wù)管理Spring允許添加聲明式事務(wù)管理來管理java對(duì)象.假設(shè)我們想確認(rèn)bank總是和一個(gè)有效的事務(wù)上下文一起被調(diào)用.我們通過在實(shí)際對(duì)象的上部配置一個(gè)proxy對(duì)象. Proxy和實(shí)際對(duì)象有相同接口,所以客戶通過完全相同的方式使用它. 配置Proxy wrap每個(gè)BankDAO方法到事務(wù)中.結(jié)果配置文件如下. 不要被XML的龐大嚇倒—大多數(shù)內(nèi)容能通過復(fù)制和粘貼到你自己的工程中再使用.
<?xml version="1.0" encoding="UTF-8"?>
<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>
這個(gè)XML文件告訴Spring去配置下面的對(duì)象:
1. 需要通過JDBC連接的datasource.
2. 添加jtaTransactionManager和jtaUserTransaction用于為JTA事務(wù)的Spring配置作準(zhǔn)備.
3. springTransactionManager用于告訴Spring需要使用JTA.
4. BankDAO被重命名為bankTarget (因如下解釋的原因).
5. bank對(duì)象被添加用于包裝事務(wù)和bankTarget的所有方法.我們通過配置bank對(duì)象來使用
springTransactionManager,這意味著所有事務(wù)將是JTA事務(wù). 每個(gè)事務(wù)都被設(shè)置為PROPAGATION_REQUIRED,這將在任何異常下出現(xiàn)強(qiáng)制回滾.
對(duì)這些對(duì)象包含的內(nèi)容,你都可以很容易的復(fù)制和粘貼jtaTransactionManager, jtaUserTransaction, springTransactionManager到其他工程.其他的是應(yīng)用程序相關(guān)的對(duì)象:datasource, bankTarget, bank. Bank對(duì)象很有趣:事實(shí)上對(duì)于bankTarget它是一個(gè)proxy;他們擁有相同的接口. Trick如下:當(dāng)我們的應(yīng)用程序請(qǐng)求Spring去配置和返回bank對(duì)象,Spring實(shí)際上將返回proxy(看起來和我們的應(yīng)用程序完全相同),隨后這個(gè)proxy將為我們開始/結(jié)束事務(wù).這樣,應(yīng)用程序和Bank類本身都不需要知道JTA!圖4闡述了在這步我們所得到的.
Figure 4. Architecture with declarative JTA transactions in Spring
現(xiàn)在的工作如下:
1. 應(yīng)用程序調(diào)用bank對(duì)象.這將觸發(fā)Spring的初始化處理和返回proxy對(duì)象. 對(duì)應(yīng)用程序而言,這個(gè)proxy行為和我們的Bank是一樣的.
2. 當(dāng)bank的一個(gè)方法被調(diào)用, 這個(gè)調(diào)用將會(huì)通過proxy進(jìn)行.
3. proxy使用springTransactionManager創(chuàng)建一個(gè)新事務(wù).
4. springTransactionManager被配置為使用JTA,因些它委派到JTA.
5. 調(diào)用被forward到Bank的實(shí)際對(duì)象,bankTarget.
6. bankTarget使用從Spring中得到的datasource.
7. datasource對(duì)事務(wù)進(jìn)行注冊(cè).
8. 通過規(guī)則的JDBC訪問數(shù)據(jù)庫.
9. 在返回時(shí), proxy終止事務(wù):如果在先前的序列中沒有發(fā)生異常,那么將會(huì)提交終止指令.否則,它將會(huì)被回滾.
10. transaction 管理器與數(shù)據(jù)庫配合進(jìn)行提交和回滾.
在這步中的測(cè)試怎樣進(jìn)行?我們可重用BankTest 和它清晰的事務(wù)劃分:因?yàn)镻ROPAGATION_REQUIRED, proxy將和在BankTest中創(chuàng)建的事務(wù)上下文一起執(zhí)行.
第五步:編寫PROPAGATION_REQUIRED在這步,我們將添加JMS處理邏輯.為了做到這樣,我們主要需要實(shí)現(xiàn)JMS MessageListener接口.我們也會(huì)添加公共的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() );
}
}
}
第六步:配置MessageDrivenBank這里我們配置MessageDrivenBank去監(jiān)聽事務(wù)的QueueReceiverSessionPool.這樣給我們可以實(shí)現(xiàn)和EJB(沒有丟失信息和冗余信息)類似的消息機(jī)制,但在這里我們是用簡單的POJO對(duì)象實(shí)現(xiàn).當(dāng)向pool中插入一個(gè)MessageListener,這個(gè)會(huì)話池將確保用JTA/XA事務(wù)接收到消息.結(jié)合JTA/XA-capable 的JDBC數(shù)據(jù)源,我們可以實(shí)現(xiàn)可靠的消息機(jī)制. Spring的配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!--
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>
因?yàn)檫@篇文章需要一個(gè)便于安裝的JMS服務(wù),所以這里我們使用ActiveMQ.如果你正在使用另一個(gè)JMS實(shí)現(xiàn),那么你將仍然能使用這部分提出的技術(shù).接下來除了datasource和bank對(duì)象,我們將增加下面的對(duì)象定義:
· xaFactory: 為建立JMS連接的connection工廠.
· queue: queue代表我們將使用的JMS隊(duì)列, 這個(gè)隊(duì)列被配置成ActiveMQ要求的形式.
· queueConnectionFactoryBean:一個(gè)JTA-aware的JMS連接器.
· A queueReceiverSessionPool for JTA-enabled message consumption:注意:我們同時(shí)指定了用來調(diào)用的初始化方法(例:start);這是Spring的另一個(gè)特性. Start方法在session pool類里定義,它是在Spring配置文件中進(jìn)行配置的.
· messageDrivenBank:負(fù)責(zé)處理消息.
你可以問問自己事務(wù)管理是在哪里進(jìn)行的.事實(shí)上, 在先前部分被添加的對(duì)象已消失.為什么呢?因?yàn)槲覀儸F(xiàn)在使用QueueReceiverSessionPool來接收來自JMS的消息,并且這個(gè)類也為每次接收啟動(dòng)一個(gè)JTA事務(wù).我們也可以保留JTA配置,另外添加JMS配置, 但是這樣可能會(huì)使XML文件更長. 現(xiàn)在session pool類將擔(dān)當(dāng)事務(wù)管理角色.它和proxy方法的工作相似; 只是這個(gè)類需要JMS MessageListener 為之添加事務(wù). 通過這樣配置,在每個(gè)消息收接之前程序?qū)?dòng)一個(gè)新事務(wù) 無論何時(shí), 當(dāng)我們的消息實(shí)例正常返回時(shí), 這個(gè)事務(wù)將提交. 如果出現(xiàn)RuntimeException, 那么這個(gè)事務(wù)將回滾. 結(jié)構(gòu)如下面圖5(可以清淅地看到一些JMS對(duì)象).
Figure 5. Architecture for message-driven applications in Spring
現(xiàn)在該架構(gòu)工作如下:
1. 應(yīng)用程序調(diào)用bank對(duì)象和初始化數(shù)據(jù)庫表.
2. 應(yīng)用程序queueReceiverSessionPool, 因此觸發(fā)一個(gè)start方法的調(diào)用去監(jiān)聽到達(dá)的消息.
3. queueReceiverSessionPool在隊(duì)列中偵察一個(gè)新消息.
4. queueReceiverSessionPool開始一個(gè)新事務(wù),并且注冊(cè)這個(gè)事務(wù).
5. queueReceiverSessionPool調(diào)用已注冊(cè)的MessageListener (messageDrivenBank).
6. 這將觸發(fā)對(duì)bank 對(duì)象的調(diào)用.
7. bank 對(duì)象通過datasource訪問數(shù)據(jù)庫.
8. datasource注冊(cè)事務(wù).
9. 通過JDBC訪問數(shù)據(jù)庫.
10. 當(dāng)處理完成時(shí), queueReceiverSessionPool會(huì)終止這個(gè)事務(wù)。然后進(jìn)行commint(除非發(fā)生RuntimeException).
11. transaction manager開始消息隊(duì)列的兩階段提交.
12. transaction manager開始數(shù)據(jù)庫的兩階段提交.
第七步:編寫應(yīng)用程序因?yàn)槲覀儧]有使用容器,我們僅僅提供一個(gè)Java應(yīng)用程序就可以啟動(dòng)整個(gè)銀行系統(tǒng).我們的Java應(yīng)用程序是非常簡單: 它有能力找回配置的對(duì)象(Spring通過XML文件將他們放到一起). 這個(gè)應(yīng)用程序能在任何兼容的JDK(Java Development Kit)上運(yùn)行,并且不需要應(yīng)用服務(wù)器.
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!是不是認(rèn)為J2EE也很容易呢?
對(duì)通用性的考慮這部分里我們看看更多的概念,這些概念在許多J2EE應(yīng)用程序中是很重要的.我們同樣將看到對(duì)這些概念來說,一個(gè)應(yīng)用服務(wù)器并不是必須的.
集群和可擴(kuò)展性健壯的企業(yè)應(yīng)用程序需要集群來分流負(fù)擔(dān). 在消息驅(qū)動(dòng)應(yīng)用程序的例子中,這很容易:我們自動(dòng)地從JMS應(yīng)用程序繼承得來處理能力.如果我們需要更強(qiáng)大的處理能力,那么我們只需增加更多連接相同JMS服務(wù)器的進(jìn)程.一個(gè)對(duì)服務(wù)性能有效的衡量標(biāo)準(zhǔn)是在隊(duì)列中停留的消息的數(shù)量. 在其他情況下,如基于web的 架構(gòu)(如下)我們能很容易地使用web環(huán)境下的集群能力.
方法級(jí)別的安全一個(gè)典型的觀點(diǎn)是認(rèn)為EJB能增加方法級(jí)的安全性.雖然并沒有在這篇文章中提到,但是在Sping中配置方法級(jí)別的安全是可能的.這種配置類似于我們?cè)黾臃椒?jí)的事務(wù)劃分的方式.
對(duì)非消息驅(qū)動(dòng)的應(yīng)用程序的通用性在不改變?cè)创a的情況下(除了主應(yīng)用程序類),我們使用的平臺(tái)能很容易地被整合到任何J2EE web 應(yīng)用服務(wù)器. 換句話說 , 通過JMS進(jìn)行后臺(tái)處理;這使得web服務(wù)器在面對(duì)后臺(tái)處理的延遲問題上更可靠和更獨(dú)立.在任何情況下, 為了實(shí)現(xiàn)容器管理的事務(wù)或容器管理的安全性,我們都不再需要依靠EJB容器來實(shí)現(xiàn).
關(guān)于容器管理持久化?存在并被很多開發(fā)者檢驗(yàn)過的技術(shù)例如:JDO或者Hibernate 都不一定需要一個(gè)應(yīng)用服務(wù)器. 另外,這些工具已經(jīng)占據(jù)了持久化市場.
結(jié)論今天,不需要應(yīng)用服務(wù)器的J2EE已經(jīng)成為可能,也很容易. 有人或許會(huì)說,沒有應(yīng)用程序服務(wù)器,有一些應(yīng)用程序仍不能實(shí)現(xiàn):例如,如果你需要一般的JCA(Java Connectivity API) 功能性, 那么我們上面提供的平臺(tái)是不夠的. 但是,這可能會(huì)發(fā)生改變,因?yàn)椴挥檬褂靡粋€(gè)應(yīng)用程序服務(wù)器進(jìn)行開發(fā),測(cè)式和部署的好處實(shí)在是太大. 人們?cè)絹碓较嘈? 將來得J2EE是一個(gè)模塊化的”選擇你所需要”架構(gòu). 這與我們之前的完全基于應(yīng)用服務(wù)器的方法相反. 在這樣的情況下,J2EE開發(fā)者將從應(yīng)用服務(wù)器和EJB中解放出來.