本文的大體思路是展示了一次業(yè)務交易如何動態(tài)地為子系統(tǒng)處理過程觸發(fā)業(yè)務事件。本文所示的例子使用Spring框架和Spring AOP有效地解耦業(yè)務服務和子系統(tǒng)處理功能。現(xiàn)在讓我們仔細看看業(yè)務需求。

業(yè)務需求

客戶注冊系統(tǒng)(CRS——customer registration system)在其完成客戶在線注冊后需要發(fā)送通知給它的客戶,并傳送他們的地址數(shù)據(jù)到發(fā)票系統(tǒng)(invoice system),以便為付費產生發(fā)票。

技術設計

讓我們把上面提到的業(yè)務需求分解成技術設計。本例中,我們將定義一個自定義業(yè)務事件以標示客戶注冊過程。

一個事件可以被認為是在特定時間間隔發(fā)生的一些事情。本例中,它是客戶注冊過程。典型地,當事件發(fā)生時,一個單一事件可能包含了一個或多個需要發(fā)生的動作。按照業(yè)務需求,我們已經確定了如下兩個動作:

  1. 發(fā)送郵件通知客戶。
  2. 傳送客戶地址數(shù)據(jù)到發(fā)票系統(tǒng)。

我們現(xiàn)在將設計事件數(shù)據(jù)結構以持有存儲在事件數(shù)據(jù)庫表中的信息。以下事件屬性被確定下來。

  • 事件標識符:1
  • 事件描述:客戶注冊事件
  • 動作代碼:MT

該事件標識符是映射到數(shù)據(jù)庫中的主鍵。事件描述定義了關于事件的描述信息。最后一個是動作代碼,當事件發(fā)生時,它代表了要發(fā)生的不同動作。該動作代碼定義在動作代碼對照表中。

上面提到的事件,其動作代碼標識為M和T。M代表發(fā)送一個郵件通知到客戶,T代表傳送客戶地址數(shù)據(jù)到發(fā)票系統(tǒng)。

Example: Event.java

 /**
*Event.java - The event domain object
*@author - Vigil Bose
*/
public class Event implements Serializable {

private Integer eventId;
private String eventDesc;
private String eventActionCodes;
private static final long serialVersionUID = 1L;

/** The cached hash code value for this instance. Settting to 0 triggers
re-calculation. */
private int hashValue = 0;

/**
*@return the eventActionCodes
*/
public String getEventActionCodes(){
return eventActionCodes;
}
/**
* @param eventActionCodes the eventActionCodes to set
*/
public void setEventActionCodes(String eventActionCodes) {
this.eventActionCodes = eventActionCodes;
}
/**
* @return the eventDesc
*/
public String getEventDesc() {
return eventDesc;
}
/**
* @param eventDesc the eventDesc to set
*/
public void setEventDesc(String eventDesc) {
this.eventDesc = eventDesc;
}
/**
* Return the simple primary key value that identifies this object.
* @return the eventId
*/
public Integer getEventId() {
return eventId;
}
/**
* Set the simple primary key value that identifies this object.
* @param eventId the eventId to set
*/
public void setEventId(Integer eventId) {
this.hashValue = 0;
this.eventId = eventId;
}
/**
*Implementation of the equals comparison on the basis of equality
*of the primary key values.
* @param rhs
* @return boolean
*/
public boolean equals(Object rhs){
if (rhs == null)
return false;
if (! (rhs instanceof Event))
return false;
Event that = (Event) rhs;
if (this.getEventId() == null || that.getEventId() == null)
return false;
return (this.getEventId().equals(that.getEventId()));
}

/**
* Implementation of the hashCode method conforming to the Bloch pattern with
* the exception of array properties (these are very unlikely primary key types).
* @return int
*/
public int hashCode(){
if (this.hashValue == 0){
int result = 17;
int eventIdValue = this.getEventId() == null ? 0 :
this.getEventId().hashCode();
result = result * 37 + eventIdValue;
this.hashValue = result;
}
return this.hashValue;
}
}

現(xiàn)在我們已經設計了事件領域對象以代表客戶注冊事件。現(xiàn)在我們轉而設計Web層與業(yè)務服務層之間的API契約。設計的約束之一是改變領域模型不能破壞Web層與業(yè)務服務層之間的API契約。為了滿足該設計約束,確定了兩個數(shù)據(jù)包裝類:AbstractData和UserData。AbstractData本質上定義了一些行為的抽象。UserData是AbstractData的子類,并提供了其他行為。比如,如果你有一個應用框架,抽象類可以提供諸如事件和消息處理的默認服務。

在本例中,AbstractData負責收集各種業(yè)務事件。AbstractData的子類是UserData,用來持有我們的主要領域對象(用戶對象)。

用戶領域對象由不同屬性組成,這些屬性是用來識別用戶的,如userId、firstName、lastName和加過密的password。它還包含了地址領域對象,該對象有多個地址屬性,如address line 1、address line 2、city、state等等。

Example: AbstractData.java

/**
*AbstractData.java - A template pattern like class that implments
*the event collection behavior. This class is used by all data
*transfer wrapper objects between UI Layer and Server side Layer
*@author - Vigil Bose
*/
public abstract class AbstractData{

/**
*Stores all the events identified during the transaction
*Processing.
*/
private Set eventSet = Collections.synchronizedSet(new HashSet());

/**
* @return Returns the eventSet.
*/
public Set getEventSet() {
return eventSet;
}

/**
*@param event - An instance of a business event resulted from a particular
*business transaction
*/
public void addToEventSet(Event event) {
this.eventSet.add(event);
}

}

在AbstractData中聲明一個集合(Set)的原因,是為了避免在給定時間點集合中有同一重復事件。讓我們看看UserData長什么樣吧。UserData包含了實際User領域對象。因此針對User領域對象的任何改變都被限制在這個包裝類中,不會破壞客戶端和業(yè)務服務層之間的接口契約。

Example: UserData.java

/**
*UserData.java - A concrete POJO data wrapper whose responsibility is to
*holds the main domain object reference and used between client and business
*service layers.
*@author - Vigil Bose
*/
public class UserData extends AbstractData{

private User user;

/**
* @return The user domain object instance
*/
public Users getUsers(){
return this.users;
}
/**
*@param The user instance to set.
*/
public void setUser(User user){
this.user = user;
}

}

讓我們看看業(yè)務服務接口。本例中,我們將在IRegistrationService接口中定義一個叫做doRegister()的API契約,以完成用戶注冊業(yè)務過程。該API本質上是事務型的,因為它要向多個數(shù)據(jù)庫表插入記錄。客戶層和業(yè)務層通過該接口進行交互。

Example: IRegistrationService.java

/**
*IRegistrationService.java - A classic example of EJB's business
*methods interface pattern that exposes all the Registration
*related business methods that can be implemented by both the
*enterprise session bean as well as the Pojo service. Moreover
*this pattern allows us to switch to POJO implementation later
*if it makes sense to do so.
*@author - Vigil Bose
*/
public interface IRegistrationService{

/**
*API to complete the registration business process
*@param userData - The data wrapper object
*/
public void doRegister(AbstractData userData);

}

為了簡單起見,本例中我們只用了POJO(Plain Old Java Object)服務。業(yè)務服務實現(xiàn)則實現(xiàn)了完成客戶注冊過程的業(yè)務邏輯。本例中,服務實現(xiàn)的唯一職責是將調用委派給數(shù)據(jù)訪問層(Data Access Layer),以在適當?shù)臄?shù)據(jù)庫表中記錄客戶注冊交易的狀態(tài)。

Example: RegistrationServiceImpl.java

/**
*The primary business method implementation of Customer Registration Service.
*This is a POJO. It does not depend on any Spring APIs. It's usable outside a
*Spring container, and can be instantiated using new in a JUnit test. However,
*we can still apply declarative transaction management to it using Spring AOP.
*@author - Vigil Bose
*/
public class RegistrationServiceImpl implements IRegistrationService{

private IRegistrationServiceDao registrationServiceDao;

/**
* A setter method of dependency injection
* @param registrationServiceDao - The registrationServiceDao to set.
*/
public setRegistrationServiceDao(IRegistrationServiceDao
registrationServiceDao){
this.registrationServiceDao = registrationServiceDao;
}
/**
* API to register the user
* @param user - The user domain object
*/
public void doRegister(AbstractData userData){
this.registrationServiceDao.completeRegistration(userData.getUser());
}
}

務實使用DAO模式

Data Access Object(DAO——數(shù)據(jù)訪問對象)在核心J2EE設計模式一書中被編目為一個集成層設計模式。它把持久庫存取和操作代碼封裝到了一個單獨的層次。本文中所指的持久庫就是RDBMS。

該模式在業(yè)務邏輯層和持久存儲層之間引入了一個抽象層。業(yè)務對象通過數(shù)據(jù)訪問對象訪問RDBMS(數(shù)據(jù)源)。該抽象層簡化了應用代碼并引入了靈活性。理想地,對數(shù)據(jù)源所做的變動(比如變換數(shù)據(jù)庫廠商或類型),僅僅需要改變數(shù)據(jù)訪問對象,因而對業(yè)務對象影響最小。本例中,我們使用Hibernate實現(xiàn)數(shù)據(jù)訪問策略。

DAO設計模式所提供的的靈活性主要被歸因于對象設計的最佳實踐:用接口編程。該原則規(guī)定了具體對象必須實現(xiàn)一個接口,在調用程序中使用該接口而非具體對象本身。因此,你可以容易地替換一個不同的實現(xiàn),而對客戶端代碼沖擊很小。

遵循上面所說的原則,我們將定義注冊服務DAO接口——IRegistrationServiceDao.java,它有一個completeRegistraion()行為。業(yè)務組件將通過這個接口與DAO交互。

Example: IRegistrationServiceDao.java

/**
*A POJO data access object interface for the CRS services business layer.
*The API's defined in this interface are all transactional APIs within the
*business services layer
*@author - Vigil Bose
*/
public interface IRegistrationServiceDao{
/**
* Data Access API to create the new user in the system
* @param user - The composite user domain object
*/
public void completeRegistration(User user) throws DataAccessException;
}

在定義了數(shù)據(jù)訪問接口之后,我們必須提供一個IRegistrationServiceDao的具體實現(xiàn)——RegistrationServiceDaoImpl。

本例中該實現(xiàn)中使用了Hibernate。這里所使用的模式是策略模式,可以用任何對象關系映射(Object Relation Mapping)產品或JDBC來替換。該類的職責是將客戶注冊交易的狀態(tài)記錄在數(shù)據(jù)庫表中。

Example: RegistrationServiceDaoImpl.java

/**
*The Registration Services Data Access Strategy implementation
*using Hibernate persistence mechanism that support various
*registration related business transactions.
*@author - Vigil Bose
*/
public class RegistrationServiceDaoImpl extends HibernateDaoSupport
implements IRegistrationServiceDao{

/**
* Data Access API to create the new user in the system
* @param users - The composite users domain object
*/
public void completeRegistration(Users users) throws DataAccessException {

getHibernateTemplate().save(users);
}

}

任何應用程序中,訪問只讀數(shù)據(jù)都是重要的。讓我們看一個普通java接口——ILookUpServiceDao的例子,它在CRS中被用到,其暴露了finder和getter方法以訪問只讀數(shù)據(jù)。

Example: ILookUpServiceDao.java

/**
*A POJO data access object interface that exposes the lookup API's in Customer
*Registration System.
*The API's defined in this interface can be used with or without any other
*transactional APIs within the business services layer
*@author - Vigil Bose
*/
public interface ILookUpServiceDao{
/**
* Data Access API to find the event instance based on its primary key
* @param eventId - The event tables primary key identifier
*/
public Event findEventById(Integer eventId) throws DataAccessException;

}

下例是Hibernate實現(xiàn)策略。ILookUpServiceDao接口中的API定義在具體類LookUpServiceDaoImpl中被實現(xiàn)。為了簡單,這里只顯示了一個API實現(xiàn)。

Example: LookUpServiceDaoImpl.java

/**
*A POJO data access implementation that implements the lookup API's in Customer
*Registration System.
*The API's defined in this interface can be used with any other
*transactional APIs within the business services layer
*@author - Vigil Bose
*/
public classe LookUpServiceDaoImpl extends HibernateDaoSupport
implements ILookUpServiceDao {
/**
* Data Access API to find the event instance based on its primary key
* @param eventId - The event tables primary key identifier
* @return an instance of Event domain object
* @throws DataAccessException
*/
public Event findEventById(Integer eventId) throws DataAccessException{
return (Event)getHibernateTemplate().get(Event.class, eventId);
}

}

Spring框架提供的HibernateDaoSupport類是一個模板模式實現(xiàn),其抽象了Hibernate相關API和與Hibernate Session相關的資源管理。

有了數(shù)據(jù)訪問實現(xiàn),我們對業(yè)務需求的一個方面——客戶注冊過程——感到滿意了。現(xiàn)在我們將考慮需求的第二個方面,它是在設計期間所確定的子系統(tǒng)功能。該需求是這樣的:當注冊完成時,系統(tǒng)將發(fā)送郵件通知給客戶并傳送客戶地址數(shù)據(jù)給發(fā)票系統(tǒng)以產生發(fā)票。我們將通過著名的命令模式來實現(xiàn)子系統(tǒng)處理過程。

命令模式

命令模式用來提供一個公共接口以執(zhí)行不同的命令。任何實現(xiàn)了該命令接口的類可以在其execute()方法中提供特定任務的實現(xiàn)。

在設計期間,我們已經確定了針對同一接口的兩個不同命令實現(xiàn)。就業(yè)務服務層來說,子系統(tǒng)功能是一個單獨的關注點。因此我使用AOP技術將這個單獨的關注點從主要業(yè)務服務層實現(xiàn)(RegistrationServiceImpl)中解耦。

想更多了解AOP概念的人,請點擊這里。你還可以從下面看到一些關于AOP的介紹。現(xiàn)在,讓我們設計命令接口。

Example: ICommand.java

/**
*ICommand.java - The famous command interface that exposes the execute API.
*@author - Vigil Bose
*/
public interface ICommand{

/**
*The Command design pattern encapsulates the concept of the
*command into an object. The issuer holds a reference to the
*command object rather than to the recipient.The issuer sends
*the command to the command object by executing a specific
*method on it. The command object is then responsible for
*dispatching the command to a specific recipient to get the
*job done.
*@param data - The data transfer object
*/
public void execute(Object data);

}

典型的命令實現(xiàn)提供了打包一組計算的方法(一個接收者和一組動作)并把它作為第一級別對象向周圍傳遞。該命令對象調用請求命令接收者(receiver)的方法以真正處理請求。通常也能發(fā)現(xiàn)一個命令實現(xiàn)自己處理特定任務,無須委派請求給receiver。在本例中,MailingCommandImpl通過調用EmailService實現(xiàn)了發(fā)送郵件通知的任務。為簡單起見,EmailService實現(xiàn)沒有在本例中展現(xiàn)。畢竟,我們的意圖是展現(xiàn)業(yè)務事件是如何借助于AOP和Spring2.0被路由到子系統(tǒng)處理器的。

Example: MailingCommandImpl.java

/**
*MailingCommandImpl.java - A command implementation that implements
*the task of sending mail notification to the customer who completed
*the registration process.
*@author - Vigil Bose
*/
public class MailingCommandImpl implements ICommand{

private IEmailService emailService;

/**
*A setter method of dependency injection
*@param emailService - The emailService instance to set.
*/
public void setEmailService(IEmailService emailService){
this.emailService = emailService;
}
/**
*API execute is used to execute the mailing tasks implemented
*@param args - An instance of AbstractData used in business service layer
*/
public void execute(Object args){

//get the reference of user object
User user = (User)args;

//get the reference of address object via its parent object User.
Address address = user.getAddress()

//Invoke the EmailService API here to send out the notifications....

}
}

現(xiàn)在,我將設計第二個命令實現(xiàn),它將幫助我們實現(xiàn)關于傳送客戶地址數(shù)據(jù)到發(fā)票應用的業(yè)務需求。在這個特定實現(xiàn)中,我們可以選擇任何協(xié)議(如Web服務、消息傳遞,或 XML over Http等等)來發(fā)送客戶信息到發(fā)票應用(假設發(fā)票應用可以使用上面提到的任何協(xié)議用作應用集成)。為了簡單,下面給出的是使用JMS消息傳遞的例子。本例并沒有展示JMS消息傳遞的內部結構。

Example: SendCustomerInfoCommandImpl.java

 /**
*SendCustomerInfoCommandImpl.java - A command implementation that implements
*the task of transmiting the customer's address data to the invoice system.
*@author - Vigil Bose
*/
public class SendCustomerInfoCommandImpl implements ICommand{

private IMessagingService messagingService;

/**
* A setter method of dependency injection
*/
public void setMessagingService(IMessagingService messagingService){
this.messagingService = messagingService;
}

/**
*API execute is used to execute the messaging task implemented.
*@param args - An instance of AbstractData used in the business service layer
*/
public void execute(Object args){

User user = (User)args;

//Invoke the appropriate messagingService API
//to send the customer information here....
}
}

AOP的基本概念

AOP也被稱為Aspect Oriented Programming(面向方面編程)試圖幫助程序員分離關注點,尤其是橫向切面關注點(cross-cutting concerns)。過程、包、類及方法都是幫助程序員把關注點封裝到單一實體內。但是有些關注點不適合這種形式的封裝。我們稱之為橫向切面關注點,是因為它們橫跨了程序中許多模塊。它可能使代碼分散或纏結(scattered or tangled),使人們更難理解和維護。當一個關注點(例如本利的事件路由)蔓延到許多模塊(類和方法)時,代碼被分散了。這意味著修改事件分發(fā)功能可能需要修改所有受影響的模塊。

代碼失去了典雅和簡單,因為各種新的關注點已經與基本功能(有時稱為業(yè)務邏輯關注點)纏結在一起。事務、消息傳遞、安全以及日志都是橫向切面關注點的例子。

AOP試圖通過讓程序員在一個單獨的稱之為aspect的模塊中表達橫向切面關注點來解決這些問題。Aspect可以包含advice(加入到程序指定點的代碼)和inter-type聲明(增加到其他類的結構成員)。例如,一個安全模塊可以包含在訪問一個銀行賬戶前執(zhí)行安全檢查的advice。pointcut定義了一個銀行賬戶能被訪問的時機(加入點,join points),而在advice體內的代碼定義了安全檢查是如何實現(xiàn)的。使用這種方式,檢查代碼和位置點可以在一個地方維護。更進一步,好的pointcut可以預見后期程序變動,因此如果另一個開發(fā)者創(chuàng)建了一個新的方法來訪問銀行賬戶,在其執(zhí)行時advice將應用到該新方法上。占據(jù)領導地位的AOP實現(xiàn)是AspectJ、AspectWorkz、Spring AOP等等。

Spring AOP用純Java實現(xiàn)。不需要特殊編譯處理。AspectJ需要特殊編譯處理。Spring AOP不需要控制各層級類裝載器,因而適合用在J2EE web容器或應用服務器中。Spring 2.0還提供了與AspectJ的緊密集成。

事件路由

為了滿足我們的業(yè)務需求,我以及確定了兩個AOP組件。它們是RegistrationBeforeAdvice和RegistrationAfterAdvice。請參考Spring參考文檔以獲得更多關于各種AOP advice和其他概念。

識別出兩個AOP組件背后的基本原理是支持事件路由和在代碼中最小化交叉依賴。RegistrationBeforeAdvice的職責被限制在識別和收集適當?shù)臉I(yè)務事件。該before advice的Spring AOP實現(xiàn)可以被配置在Spring應用上下文配置文件(Spring application context file)中,以截獲業(yè)務服務接口API來注入定制行為——識別并增加正確的業(yè)務事件到事件集合中。

本例中,RegistrationBeforAdvice截獲業(yè)務服務接口的doRegister(AbstractData data)API。該advice訪問該服務接口API的入?yún)ⅲˋbstractData)。早期在AbstractData層實現(xiàn)的事件集合在這里也變得垂手可得。RegistrationBeforeAdvice識別恰當?shù)臉I(yè)務事件并把它增加到event集合中。

Spring應用上下文中的eventMap配置是一個全局事件映射(global event map)。eventKey將適當?shù)臉I(yè)務服務接口API名稱映射到事件標識符。這讓我們可以在全局事件映射配置中無縫地將一個定義在業(yè)務服務接口的新的業(yè)務API映射到一個事件id,而無需修改RegistrationBeforeAdvice AOP組件的任何代碼。然而,對這種方法有一個警告。當程序員犯了錯誤,在全局事件映射配置中配置了錯誤的方法名到eventId,這種錯誤在編譯期間并不容易發(fā)現(xiàn)。但是一個簡單的Junit測試即可發(fā)現(xiàn)這種用詞不當?shù)腻e誤。

業(yè)務API名稱:doRegister
Event Id: 1

映射另一個業(yè)務API名,如doUpdate(),到另一個值為2的事件id現(xiàn)在變得非常容易了。我們所要改變的只是在接口中定義新的API之后,在Spring應用上下文配置文件的事件映射中增加一個映射即可。下例給出了配置片斷。

<!-- creates a java.util.Map instance with values loaded from
the supplied 'sourceMap'.Global Event Map. The values are mapped
in event table. The keys are matched with the business API name-->
<util:map id="eventMap">
<entry key="doRegister">
<value type="java.lang.Integer">1</value></entry>
<entry key="doUpdate">
<value type="java.lang.Integer">2</value></entry>
</util:map>

在某些情況下,單個業(yè)務處理可能導致多個事件。這仍只需輕微修改RegistrationAfterAdvice(譯注:疑為RegistrationBeforeAdvice )的代碼及事件映射配置即可。這種情況下,我們需要說明每個業(yè)務交易的事件列表。為簡單起見,本文例中只限于展示一個業(yè)務交易僅有一個事件的情況。

請參考下面的代碼樣例,實際看看before advice。

Example: RegistrationBeforeAdvice.java

/**
*RegistrationBeforeAdvice.java - This advise acts before the doRegister() API in
*the business service interface executes and sets the appropriate event in the
*eventSet collection implemented in the AbstractData layer. The event is Customer
*Registered Event. This advice inserts custom behavior in IRegistrationService API
*doRegister() before it is executed and identifies the correct business event add
*it to the event collection
*@author - Vigil Bose
*/
public class RegistrationBeforeAdvice implements MethodBeforeAdvice{

private Map eventMap;
private ILookUpServiceDao lookUpServiceDao;

/**
* A setter method of dependency injection
* @param Map - The eventMap instance to set.
*/
public void setEventMap(Map eventMap){
this.eventMap = eventMap;
}
/**
* A setter method of dependency injection
* @param lookUpServiceDao - The lookUpServiceDao instance to set.
*/
public void setLookUpServiceDao(ILookUpServiceDao lookUpServiceDao){
this.lookUpServiceDao = lookUpServiceDao;
}
/**
*Before advice can insert custom behavior before the join point
*executes, but cannot change the return value.If a before advice
*throws an exception, this will abort further execution of the
*interceptor chain. The exception will propagate back up the
*interceptor chain. If it is unchecked, or on the signature of the
*invoked method, it will be passed directly to the client; otherwise
*it will be wrapped in an unchecked exception by the AOP proxy.
*@param method - method being invoked
*@param args - arguments to the method
*@param target - target of the method invocation. May be null
*@throws Throwable - if this object wishes to abort the call.
*Any exception thrown will be returned to the caller if it's allowed
*by the method signature. Otherwise the exception will be wrapped
*as a runtime exception
*@see org.springframework.aop.MethodBeforeAdvice#before(java.lang.reflect.Method
*java.lang.Object[], java.lang.Object)
*/
public void before(Method method, Object[] args, Object target) throws Throwable {

AbstractData data = (AbstractData)args[0];

Set keySet = this.eventMap.keySet();
Integer eventId;

Event event = null;
String eventKey = null;

//Iterate through the key set and extract event identifier and
//retrieve the event from the database and add it to the event
//collection.

for (Iterator iter = keySet.iterator(); iter.hasNext();) {
eventKey = (String) iter.next();

//Check whether the eventKey is matched with the business
//service interface API name. If it does, extract the eventId
//and retrieve the event instance from the datastore and add it
//to the event collection.

if (eventKey.equalsIgnoreCase(method.getName()){
eventId = (Integer)eventMap.get(eventKey);

event = this.lookupService.findEventById(Integer eventId);
data.addToEventSet(event);
}
}
}
}

本例中,一個需考慮的設計限制是Before advice或After advice組件拋出的異常應該不影響在線業(yè)務交易。在線客戶不應受到事件路由錯誤的懲罰。為簡化起見,我沒有在該例中展示如何處理異常。

RegistrationAfterAdvice負責迭代事件集合、動作代碼以及初始化路由過程。本例中使用的動作代碼是M和T。在Spring應用上下文配置文件中每一個動作代碼都有命令與之映射。RegistrationAfterAdvice通過每個與事件(客戶注冊事件)相關聯(lián)的動作代碼及獲得映射的命令對象實例對事件集合進行迭代。一旦命令對象引用被獲得,路由自動地發(fā)生,通過傳遞客戶數(shù)據(jù)給每個命令實現(xiàn)以執(zhí)行適當?shù)娜蝿铡?

Example: RegistrationAfterAdvice.java

/**
*RegistrationAfterAdvice.java - This advise acts after when the doRegister()
*API of the business service implementation is executed. This advise will
*actually delegate the event actions associated with the Customer Registered
*Event to the appropriate command. This advice inserts custom behavior to
*IRegistrationService interface API's after the API is executed.
*@author - Vigil Bose
*/
public class RegistrationAfterAdvice implements AfterReturningAdvice {
/**
*After returning advice is invoked only on normal method return,
*not if an exception is thrown. Such advice can see the return
*value, but cannot change it.
*
*@param returnValue - the value returned by the method, if any
*@param method - method being invoked
*@param args - arguments to the method
*@param target - target of the method invocation. May be null
*@throws Throwable - if this object wishes to abort the call.
*Any exception thrown will be returned to the caller if it's allowed
*by the method signature. Otherwise the exception will be wrapped as a runtime
*exception
*@see org.springframework.aop.AfterReturningAdvice#afterReturning
*(java.lang.Object, java.lang.reflect.Method, java.lang.Object[],
*java.lang.Object)
*/
public void afterReturning(Object returnValue, Method method, Object[] args,
Object target) throws Throwable {

AbstractData data = (AbstractData)args[0];
User userInfo = (User)data.getUser();
Set eventSet = data.eventSet();

Set keySet = this.commandMap.keySet();
Event event = null;
String actionCodes = null;
String actionCode = null;

//Iterate through the event set
for (Iterator iter = eventSet.iterator(); iter.hasNext();) {

event = (Event) iter.next();
actionCodes = event.getEventActionCodes();

//Loop through the action codes and extract each command mapped to
//the action code in spring application context file.

for (int i=0; i < actionCodes.length();i++){
actionCode = new Character(eventActionCodes.charAt(i)).toString();
  command = (ICommand)commandMap.get(actionCode);
if (command != null){
command.execute(userInfo);
}
}
}
}
}

在上面例子中可以看到,我本可以在RegistrationAfterAdvice本身實現(xiàn)事件集合和事件路由機制。但為了保持事件集合和事件路由的責任分離,我決定使用兩個AOP advice組件處理子系統(tǒng)路由功能。

完整配置

現(xiàn)在,在Spring應用上下文xml文件中將所有配置都串起來。在下面的例子中,Hibernate實現(xiàn)被用作數(shù)據(jù)訪問層的數(shù)據(jù)關系映射(OR映射)。當應對多個資源提供者(如數(shù)據(jù)庫和JMS)時,推薦使用JtaTransation策略。為簡單起見,下例中只展示了本地事務策略。

Example: applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
       http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util-2.0.xsd
       http://www.springframework.org/schema/jee
       http://www.springframework.org/schema/jee/spring-jee-2.0.xsd">

     <bean id="registrationService"  class="RegistrationServiceImpl">
            <property name="registrationServiceDao" ref="registrationServiceDao"/>
    </beans>

     <bean id="registrationServiceDao"  class="RegistrationServiceDaoImpl">
            <property name="sessionFactory" ref="sessionFactory"/>
    </beans>

     <bean id="lookUpServiceDao"  class="LookUpServiceDaoImpl">
            <property name="sessionFactory" ref="sessionFactory"/>
    </beans>

     <bean id="sessionFactory"
  class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"/>
       <property> name="dataSource" ref="dataSource"/>
       <!-- Use the following property jtaTransactionManager when dealing
       with CMT transactions -->
  <!-- <property name="jtaTransactionManager" ref="jtaTransactionManager"/>-->
      <property name="hibernateProperties">
       <props>
           <prop key="hibernate.dialect">org.hibernate.dialect.Oracle9Dialect</prop>
           <prop key="hibernate.connection.pool_size">3</prop>
           <prop key="hibernate.show_sql">true</prop>
           <prop key="hibernate.generate_statistics">true</prop>
           <prop key="hibernate.cache.use_structured_entries">true</prop>
           <prop key="hibernate.max_fetch_depth">3</prop>
           <prop key="hibernate.cache.provider_class">
                     org.hibernate.cache.EhCacheProvider</prop>
           <prop key="hibernate.cache.region_prefix">node1</prop>
       </props>
       </property>
       <property name="mappingResources">
       <list>
             <value>Users.hbm.xml</value>
             <value>Address.hbm.xml</value>
       </list>
      </property>
    </bean>

      <bean>id="transactionManager"
  class="org.springframework.orm.hibernate3.HibernateTransactionManager">
       <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

      <!-- <bean id="jtaTransactionManager"
  class="org.springframework.jndi.JndiObjectFactoryBean"> -->
    <!--The JNDI name of TransactionManager published in OC4J Container in 10g-->
    <!-- <property name="jndiName"
          value="java:comp/pm/TransactionManager"/>
   </bean> -->

   <!-- Transaction manager that delegates to JTA for ultimate coordinate of  transactions -->
    <!-- <bean id="transactionManager"
     class="org.springframework.transaction.jta.JtaTransactionManager"/>-->

     <aop:config>
        <!--Format: execution(modifiers-pattern? ret-type-pattern
         declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)-->

         <!--The pointcut expression here is the execution of any public method
        defined by the IRegistrationService interface-->

         <aop:pointcut id="registrationServicePointcut"
             expression="execution(public * *..IRegistrationService.*(..))"/>
                     <!--
        Here: applying the advice named "registrationBeforeAdvice" to all methods        on classes named RegistrationServiceImpl.
        -->
        <aop:advisor pointcut-ref="registrationServicePointcut"
                  advice-ref="registrationBeforeAdvice" order="1"/> 

                 <!--
        This definition creates auto-proxy infrastructure based on the given
        pointcut, expressed in AspectJ pointcut language. Here: applying the
        advice named "registrationServiceTransactionAdvice" to all methods
        on classes named RegistrationServiceImpl.-->
        <aop:advisor pointcut-ref="registrationServicePointcut"
              advice-ref="registrationServiceTransactionAdvice" order="2"/>

                        <!--
       This definition creates auto-proxy infrastructure based on the given
       pointcut,expressed in AspectJ pointcut language. Here: applying the
       advice named "registrationAfterAdvice" to all methods on
       classes named RegistrationServiceImpl.
       -->
       <aop:advisor pointcut-ref="registrationServicePointcut"
              advice-ref="registrationAfterAdvice" order="3"/>

                 </aop:config>
      <!--
        Transaction advice definition, based on method name patterns.
        Defaults to PROPAGATION_REQUIRED for all methods whose name starts with
        "do". By default, the transaction is rolled back for runtime
        exceptions including DataAccessException.
     -->
     <tx:advice id="registrationServiceTransactionAdvice"
                  transaction-manager="transactionManager">
            <tx:attributes>
                <tx:method name="do*"/>
            </tx:attributes>
     </tx:advice>

      <bean id="registrationBeforeAdvice" class="RegistraionBeforeAdvice">
            <property name="order" value="1"/>
            <property name="eventMap" ref="eventMap"/>
            <property name="lookUpServiceDao" ref="lookUpServiceDao"/>
                 </bean>

         <bean id="registrationAfterAdvice"
                      class="RegistrationAfterAdvice">
              <property name="order" value="3"/>
              <property name="commandMap">
                 <map>
                     <entry key="M"><ref bean="mailingCommand"/></entry>
                     <entry key="T"><ref bean="sendCustomerInfoCommand"/> </entry>
                 </map>
            </property>
       </beans>

      <bean id="mailingCommand" class="MailingCommandImpl">
             <property name="emailService" ref="emailService"/>
      </beans>

        <bean id="sendCustomerInfoCommand" class="SendCustomerInfoCommandImpl">
              <property name="messagingService" ref="messagingService"/>
       </beans>

     <bean id="messagingService"    class="MessagingService">
               <property name="jmsTemplate" ref="jmsTemplate"/>
        </beans>

     <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
             <property name="connectionFactory" ref="defaultQueueConnectionFactory" />
         <property name="defaultDestination" ref="defaultQueueDestination" />
    </bean>

     <!-- JNDI Lookup configuration for  event queue connection factory
     used in messaging  -->
    <jee:jndi-lookup id="defaultQueueConnectionFactory" jndi-name="EVENT_QUEUE"/>
    <!-- JNDI Lookup configuration for  event queue destination used in messaging --> 
    <jee:jndi-lookup id="defaultQueueDestination" jndi-name="EVENTQueue"/>

     <!-- JNDI Lookup configuration for DataSource in J2EE environments -->
    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/userDS"/>

     <bean id="mailSender"
  class="org.springframework.mail.javamail.JavaMailSenderImpl">
        <property name="host" value="localhost"/>
     </bean>    
    
     <bean id="emailService" class="EmailServicesImpl">       
   <property name="mailSender" ref="mailSender"/>
     </bean>

     <!-- creates a java.util.Map instance with values loaded from
        the supplied 'sourceMap'.Global Event Map. The values are mapped
        in event table. The keys are matched with the business API name-->
    <util:map id="eventMap">
        <entry key="doRegister">
              <value type="java.lang.Integer">1</value></entry>
   </util:map>
</beans>

AOP——Aspect Oriented Programming是程序設計中相對比較新的概念。該技術是對面向對象技術的補充,并帶來了更多強大能力和關注點的分離,而這些正是面向對象技術的弱項。

結論

關注點分離是開發(fā)面向服務架構的關鍵原則。它需要被分別應用到基礎架構層和實現(xiàn)層。本文中,我們示范了如何使用Spring框架的依賴注入原則和AOP特性分離出橫向切面關注點。正如你已經在例子代碼中看到的,使用這一方法能讓我們把處理服務每個關注點的代碼的交叉依賴減到最小。

參考

Spring參考文檔: http://www.springframework.org/docs/reference/


免責

請將所提供的代碼更多的視為示范使用的例子,而非被證明準備就緒的產品。如果你使用了這些代碼并發(fā)現(xiàn)有任何問題,請通知我以便更新。


查看英文原文:Dynamic Routing Using Spring framework and AOP