使用Spring框架和AOP实现动态路由
作者 Vigil Bose
Java
主题 设计,
AOP
本文的大体思路是展示了一次业务交易如何动态地为子系统处理过程触发业务事件。本文所示的例子使用Spring框架和Spring AOP有效地解耦业务服务和子系统处理功能。现在让我们仔细看看业务需求。
业务需求
客户注册系统(CRS——customer registration system)在其完成客户在线注册后需要发送通知给它的客户,并传送他们的地址数据到发票系统(invoice system),以便为付费产生发票。
技术设计
让我们把上面提到的业务需求分解成技术设计。本例中,我们将定义一个自定义业务事件以标示客户注册过程。
一个事件可以被认为是在特定时间间隔发生的一些事情。本例中,它是客户注册过程。典型地,当事件发生时,一个单一事件可能包含了一个或多个需要发生的动作。按照业务需求,我们已经确定了如下两个动作:
- 发送邮件通知客户。
- 传送客户地址数据到发票系统。
我们现在将设计事件数据结构以持有存储在事件数据库表中的信息。以下事件属性被确定下来。
- 事件标识符:1
- 事件描述:客户注册事件
- 动作代码:MT
该事件标识符是映射到数据库中的主键。事件描述定义了关于事件的描述信息。最后一个是动作代码,当事件发生时,它代表了要发生的不同动作。该动作代码定义在动作代码对照表中。
上面提到的事件,其动作代码标识为M和T。M代表发送一个邮件通知到客户,T代表传送客户地址数据到发票系统。
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;
}
}
现在我们已经设计了事件领域对象以代表客户注册事件。现在我们转而设计Web层与业务服务层之间的API契约。设计的约束之一是改变领域模型不能破 坏Web层与业务服务层之间的API契约。为了满足该设计约束,确定了两个数据包装类:AbstractData和UserData。 AbstractData本质上定义了一些行为的抽象。UserData是AbstractData的子类,并提供了其他行为。比如,如果你有一个应用框 架,抽象类可以提供诸如事件和消息处理的默认服务。
在本例中,AbstractData负责收集各种业务事件。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领域对象的任何改变都被限制在这个包装类中,不会破坏客户端和业务服务层之间的接口契 约。
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;
}
}
让我们看看业务服务接口。本例中,我们将在IRegistrationService接口中定义一个叫做doRegister()的API契约,以完成用户注册业务过程。该API本质上是事务型的,因为它要向多个数据库表插入记录。客户层和业务层通过该接口进行交互。
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)服务。业务服务实现则实现了完成客户注册过程的业务逻辑。本例中,服务实现的唯一职责是将调用委派给数据访问层(Data Access Layer),以在适当的数据库表中记录客户注册交易的状态。
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, 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——数据访问对象)在核心J2EE设计模式一书中被编目为一个集成层设计模式。它把持久库存取和操作代码封装到了一个单独的层次。本文中所指的持久库就是RDBMS。
该模式在业务逻辑层和持久存储层之间引入了一个抽象层。业务对象通过数据访问对象访问RDBMS(数据源)。该抽象层简化了应用代码并引入了灵活 性。理想地,对数据源所做的变动(比如变换数据库厂商或类型),仅仅需要改变数据访问对象,因而对业务对象影响最小。本例中,我们使用Hibernate 实现数据访问策略。
DAO设计模式所提供的的灵活性主要被归因于对象设计的最佳实践:用接口编程。该原则规定了具体对象必须实现一个接口,在调用程序中使用该接口而非具体对象本身。因此,你可以容易地替换一个不同的实现,而对客户端代码冲击很小。
遵循上面所说的原则,我们将定义注册服务DAO接口——IRegistrationServiceDao.java,它有一个completeRegistraion()行为。业务组件将通过这个接口与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;
}
在定义了数据访问接口之后,我们必须提供一个IRegistrationServiceDao的具体实现——RegistrationServiceDaoImpl。
本例中该实现中使用了Hibernate。这里所使用的模式是策略模式,可以用任何对象关系映射(Object Relation Mapping)产品或JDBC来替换。该类的职责是将客户注册交易的状态记录在数据库表中。
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);
}
}
任何应用程序中,访问只读数据都是重要的。让我们看一个普通java接口——ILookUpServiceDao的例子,它在CRS中被用到,其暴露了finder和getter方法以访问只读数据。
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实现策略。ILookUpServiceDao接口中的API定义在具体类LookUpServiceDaoImpl中被实现。为了简单,这里只显示了一个API实现。
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类是一个模板模式实现,其抽象了Hibernate相关API和与Hibernate Session相关的资源管理。
有了数据访问实现,我们对业务需求的一个方面——客户注册过程——感到满意了。现在我们将考虑需求的第二个方面,它是在设计期间所确定的子系统功 能。该需求是这样的:当注册完成时,系统将发送邮件通知给客户并传送客户地址数据给发票系统以产生发票。我们将通过著名的命令模式来实现子系统处理过程。
命令模式
命令模式用来提供一个公共接口以执行不同的命令。任何实现了该命令接口的类可以在其execute()方法中提供特定任务的实现。
在设计期间,我们已经确定了针对同一接口的两个不同命令实现。就业务服务层来说,子系统功能是一个单独的关注点。因此我使用AOP技术将这个单独的关注点从主要业务服务层实现(RegistrationServiceImpl)中解耦。
想更多了解AOP概念的人。你还可以从下面看到一些关于AOP的介绍。现在,让我们设计命令接口。
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);
}
典型的命令实现提供了打包一组计算的方法(一个接收者和一组动作)并把它作为第一级别对象向周围传递。该命令对象调用请求命令接收者 (receiver)的方法以真正处理请求。通常也能发现一个命令实现自己处理特定任务,无须委派请求给receiver。在本例中, MailingCommandImpl通过调用EmailService实现了发送邮件通知的任务。为简单起见,EmailService实现没有在本例 中展现。毕竟,我们的意图是展现业务事件是如何借助于AOP和Spring2.0被路由到子系统处理器的。
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....
}
}
现在,我将设计第二个命令实现,它将帮助我们实现关于传送客户地址数据到发票应用的业务需求。在这个特定实现中,我们可以选择任何协议(如Web服 务、消息传递,或 XML over Http等等)来发送客户信息到发票应用(假设发票应用可以使用上面提到的任何协议用作应用集成)。为了简单,下面给出的是使用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),使人们更难理解和维护。当一个关注点(例如本利的事件路由)蔓延到许多模块(类和方法)时,代码被分散了。这意味着修改事件分发功能可 能需要修改所有受影响的模块。
代码失去了典雅和简单,因为各种新的关注点已经与基本功能(有时称为业务逻辑关注点)缠结在一起。事务、消息传递、安全以及日志都是横向切面关注点的例子。
AOP试图通过让程序员在一个单独的称之为aspect的模块中表达横向切面关注点来解决这些问题。Aspect可以包含advice(加入到程序 指定点的代码)和inter-type声明(增加到其他类的结构成员)。例如,一个安全模块可以包含在访问一个银行账户前执行安全检查的advice。 pointcut定义了一个银行账户能被访问的时机(加入点,join points),而在advice体内的代码定义了安全检查是如何实现的。使用这种方式,检查代码和位置点可以在一个地方维护。更进一步,好的 pointcut可以预见后期程序变动,因此如果另一个开发者创建了一个新的方法来访问银行账户,在其执行时advice将应用到该新方法上。占据领导地 位的AOP实现是AspectJ、AspectWorkz、Spring AOP等等。
Spring AOP用纯Java实现。不需要特殊编译处理。AspectJ需要特殊编译处理。Spring AOP不需要控制各层级类装载器,因而适合用在J2EE web容器或应用服务器中。Spring 2.0还提供了与AspectJ的紧密集成。
事件路由
为了满足我们的业务需求,我以及确定了两个AOP组件。它们是RegistrationBeforeAdvice和RegistrationAfterAdvice。请参考Spring参考文档以获得更多关于各种AOP advice和其他概念。
识别出两个AOP组件背后的基本原理是支持事件路由和在代码中最小化交叉依赖。RegistrationBeforeAdvice的职责被限制在识 别和收集适当的业务事件。该before advice的Spring AOP实现可以被配置在Spring应用上下文配置文件(Spring application context file)中,以截获业务服务接口API来注入定制行为——识别并增加正确的业务事件到事件集合中。
本例中,RegistrationBeforAdvice截获业务服务接口的doRegister(AbstractData data)API。该advice访问该服务接口API的入参(AbstractData)。早期在AbstractData层实现的事件集合在这里也变 得垂手可得。RegistrationBeforeAdvice识别恰当的业务事件并把它增加到event集合中。
Spring应用上下文中的eventMap配置是一个全局事件映射(global event map)。eventKey将适当的业务服务接口API名称映射到事件标识符。这让我们可以在全局事件映射配置中无缝地将一个定义在业务服务接口的新的业 务API映射到一个事件id,而无需修改RegistrationBeforeAdvice AOP组件的任何代码。然而,对这种方法有一个警告。当程序员犯了错误,在全局事件映射配置中配置了错误的方法名到eventId,这种错误在编译期间并 不容易发现。但是一个简单的Junit测试即可发现这种用词不当的错误。
业务API名称:doRegister
Event Id: 1
映射另一个业务API名,如doUpdate(),到另一个值为2的事件id现在变得非常容易了。我们所要改变的只是在接口中定义新的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>
在某些情况下,单个业务处理可能导致多个事件。这仍只需轻微修改RegistrationAfterAdvice(译注:疑为 RegistrationBeforeAdvice )的代码及事件映射配置即可。这种情况下,我们需要说明每个业务交易的事件列表。为简单起见,本文例中只限于展示一个业务交易仅有一个事件的情况。
请参考下面的代码样例,实际看看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组件抛出的异常应该不影响在线业务交易。在线客户不应受到事件路由错误的惩罚。为简化起见,我没有在该例中展示如何处理异常。
RegistrationAfterAdvice负责迭代事件集合、动作代码以及初始化路由过程。本例中使用的动作代码是M和T。在Spring应 用上下文配置文件中每一个动作代码都有命令与之映射。RegistrationAfterAdvice通过每个与事件(客户注册事件)相关联的动作代码及 获得映射的命令对象实例对事件集合进行迭代。一旦命令对象引用被获得,路由自动地发生,通过传递客户数据给每个命令实现以执行适当的任务。
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本身实现事件集合和事件路由机制。但为了保持事件集合和事件路由的责任分离,我决定使用两个AOP advice组件处理子系统路由功能。
完整配置
现在,在Spring应用上下文xml文件中将所有配置都串起来。在下面的例子中,Hibernate实现被用作数据访问层的数据关系映射(OR映 射)。当应对多个资源提供者(如数据库和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是程序设计中相对比较新的概念。该技术是对面向对象技术的补充,并带来了更多强大能力和关注点的分离,而这些正是面向对象技术的弱项。
结论
关注点分离是开发面向服务架构的关键原则。它需要被分别应用到基础架构层和实现层。本文中,我们示范了如何使用Spring框架的依赖注入原则和 AOP特性分离出横向切面关注点。正如你已经在例子代码中看到的,使用这一方法能让我们把处理服务每个关注点的代码的交叉依赖减到最小。
参考
Spring参考文档: http://www.springframework.org/docs/reference/