科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道用Spring 2.0和AspectJ简化企业应用程序

用Spring 2.0和AspectJ简化企业应用程序

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

在本文中,作者首先了介绍在典型的企业应用程序中,Spring AOP和AspectJ适用于什么地方,之后介绍在2.0中新的Spring AOP支持。

作者:Adrian Colyer译者 俞黎敏 来源:infoq.com 2007年11月18日

关键字:

  • 评论
  • 分享微博
  • 分享邮件

在本页阅读全文(共10页)

简化Web、服务和数据访问层

Spring AOP可以用来简化Web、服务和数据访问层。在本节中,我们要看两个实例:一个取自数据访问层,一个取自服务层。

假设你已经用Hibernate 3而不是用Spring HibernateTemplate支持类实现了你的数据访问层。你现在准备开始在应用程序中使用Spring,想要在服务层中利用Spring的细粒度DataAccessException层次结构。Spring的HibernateTemplate将自动为你把HibernateExceptions转换成DataAccessExceptions,但是由于现阶段你已经有一个非常满意的数据层实现,因此并不想马上用Spring支持类对它进行重写。这意味着你需要自己实现异常转换。这个需求声明起来很简单:

从数据访问层中抛出任何HibernateException之后,在将它递给调用者之前将它转换成一个DataAccessException。

利用AOP,实现几乎与需求声明一样简单。没有AOP时实现这个需求是件非常令人头痛的事。这就是“myapp”的HibernateExceptionTranslator方面:

@Aspect
public class HibernateExceptionTranslator {
private HibernateTemplate hibernateTemplate;
public void setHibernateTemplate(HibernateTemplate aTemplate) {
this.hibernateTemplate = aTemplate;
}
@AfterThrowing(
throwing="hibernateEx",
pointcut="org.aspectprogrammer.myapp.SystemArchitecture.dataAccessOperation()"
)
public void rethrowAsDataAccessException(HibernateException hibernateEx) {
throw this.hibernateTemplate
.convertHibernateAccessException(hibernateEx);
}
}

方面需要一个HibernateTemplate,以便执行转换——我们要用依赖注入对它进行配置,就像任何其他的Spring bean一样。通知声明应该有望非常容易地理解为需求声明的一个直接转换:“@AfterThrowing从dataAccessOperation()操作中抛出一个HibernateException (hibernateEx) ,并重新抛出 rethrowAsDataAccessException”。简单有力!

我们现在可以用ajc(AspectJ编译器)构建应用程序,这样我们就完事了。但是这里不需要使用ajc,因为Spring AOP也能识别@AspectJ方面。

在应用程序上下文文件中,我们需要两个配置。首先我们要告诉Spring,包含@AspectJ方面的类型的任何bean都应该用来配置Spring AOP代理。这是通过在应用程序上下文配置文件中的任何位置声明下列元素来实现的一个一次性配置:

<aop:aspectj-autoproxy>

然后我们需要声明异常转换bean,并对它进行配置,就像对待任何一般的Spring bean一样(这里并没有任何特定于AOP的东西):

<bean id="hibernateExceptionTranslator"
class="org.aspectprogrammer.myapp.dao.hibernate.HibernateExceptionTranslator">
<property name="hibernateTemplate">
<bean class="org.springframework.orm.hibernate3.HibernateTemplate">
<constructor-arg index="0" ref="sessionFactory" />
</bean>
</property>
</bean>

仅仅因为bean的类(HibernateExceptionTranslator)是一个@AspectJ方面,就足以配置Spring AOP了。

为了完整起见,我们也看一下如何用方面声明的xml形式来完成这项工作(例如对于在JDK 1.4下进行工作的)。hibernateExceptionTranslator的bean定义与上面所述的一样。类本身不再被注解,但是它剩下的部分也完全相同:

public class HibernateExceptionTranslator {
private HibernateTemplate hibernateTemplate;
public void setHibernateTemplate(HibernateTemplate aTemplate) {
this.hibernateTemplate = aTemplate;
}
public void rethrowAsDataAccessException(HibernateException hibernateEx) {
throw this.hibernateTemplate
.convertHibernateAccessException(hibernateEx);
}
}

由于这不再是一个@AspectJ方面,我们无法使用aspectj-autoproxy元素,而是用XML定义该方面:

<aop:config>
<aop:aspect ref="hibernateExceptionTranslator">
<aop:after-throwing
throwing="hibernateEx"
pointcut="org.aspectprogrammer.myapp.SystemArchitecture.dataAccessOperation()"
method="rethrowAsDataAccessException"/>
</aop:aspect>
</aop:config>

这看起来与前一个版本一样:after-throwing 从dataAccessOperation操作中抛出hibernateEx,并且重新抛出rethrowAsDataAccessException。注意aop:aspect元素的“ref”属性,它引用了我们前面定义的hibernateExceptionTranslator bean。这是rethrowAsDataAccessException方法将要在那里被调用的bean实例,而hibernateEx则是在该方法中声明的参数名(这个例子中的唯一参数)。就是这样。我们已经实现了需求(两次!)。利用@AspectJ风格,我们有15个非空的代码行,和一行XML。这足以为我们在整个数据访问层中提供一致、正确的行为,但是它可能很大。

这个特殊方面的一大好处在于,如果你以后想要将数据层移植到一个基于利用Hibernate的实现、或者任何其他JPA实现的JPA(EJB 3持久化),你的服务层将不会受到影响,并且可以继续使用DataAccessExceptions(Spring将为JPA提供模板和异常转换,就像对其他的ORM实现所做的一样)。

既然我们可以在服务层中使用细粒度的DataAccessExceptions了,就可以利用这一点做些有意义的事情。让我们在将失败传递给客户端之前,实现由于并发失败而失败的任何等幂服务操作都将被透明地重试可设定次数的横切需求。

以下是完成这项工作的一个方面:

@Aspect
public class ConcurrentOperationExecutor implements Ordered {
private static final int DEFAULT_MAX_RETRIES = 2;
private int maxRetries = DEFAULT_MAX_RETRIES;
private int order = 1;
private boolean retryOnOptimisticLockFailure = false;
/**
* configurable number of retries
*/
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
/**
* Whether or not optimistic lock failures should also be retried.
* Default is not to retry transactions that fail due to optimistic
* locking in case we overwrite another user's work.
*/
public void setRetryOnOptimisticLockFailure(boolean retry) {
this.retryOnOptimisticLockFailure = retry;
}
/**
* implementing the Ordered interface enables us to specify when
* this aspect should run with respect to other aspects such as
* transaction management. We give it the highest precedence
* (1) which means that the retry logic will wrap the transaction
* logic - we need a fresh transaction each time.
*/
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
/**
* For now, just assume that all business services are idempotent
*/
@Pointcut("org.aspectprogrammer.myapp.SystemArchitecture.businessService()")
public void idempotentOperation() {}
@Around("idempotentOperation()")
public Object doConcurrentOperation(ProceedingJoinPoint pjp)
throws Throwable {
int numAttempts = 0;
ConcurrencyFailureException failureException;
do {
try {
return pjp.proceed();
}
catch(OptimisticLockingFailureException ex) {
if (!this.retryOnOptimisticLockFailure) {
throw ex;
}
else {
failureException = ex;
}
}
catch(ConcurrencyFailureException ex) {
failureException = ex;
}
}
while(numAttempts++ < this.maxRetries);
throw lockFailureException;
}
}

这个方面还是可以被Spring AOP或者AspectJ使用,这一点不变。around advice (doConcurrentOperation)采用了类型ProceedingJoinPoint的一个特殊参数。当proceed在这个对象中被调用时,无论“around”什么样的通知(在这个例子中为服务操作)都将执行。如果你去掉注释和样板getters-and-setters,这个方面的业务端仍然只有32行代码。由于我们在配置文件中已经有aspectj-autoproxy元素,我们需要增加的就只是一个简单的bean定义了:

<bean id="concurrentOperationExecutor"
class="org.aspectprogrammer.myapp.service.impl.ConcurrentOperationExecutor">
<property name="maxRetries" value="3"/>
<property name="order" value="1"/>
</bean>

如果服务层中并非所有的操作都是等幂的,该怎么办?我们如何判断等幂的操作呢?这就是切入点语言的威力开始显现之处。我们已经有一个表示等幂操作的概念的抽象:

@Pointcut("org.aspectprogrammer.myapp.SystemArchitecture.businessService()")

如果我们想要改变构成表示等幂操作的东西,我们所要做的就是改变切入点。例如,我们可以给等幂操作定义一个标识注解:@Idempotent。我们可以非常简单地将切入点表达式改为只与包含Idempotent注解的业务服务相匹配:

@Pointcut(
"org.aspectprogrammer.myapp.SystemArchitecture.businessService() &&
@annotation(org.aspectprogrammer.myapp.Idempotent)")
public void idempotentOperation() {}

现在比使用APT简单一些了!切入点只说:“idempotentOperation是有着Idempotent 注解的businessService”。

希望你的大多数服务操作都等幂的。在这种情况下,注解等幂的操作就可能比挑出等幂操作要容易得多。像@IrreversibleSideEffects这样的东西应该会成功。这在技术上和心理上都说得过去(指想要用IrreversibleSideEffects对他们的代码进行注解的人!我宁愿重写代码而避免使用它们;)。由于idempotentOperation的定义只有一处,很容易改变:

@Pointcut(
"org.aspectprogrammer.myapp.SystemArchitecture.businessService() &&
!@annotation(org.aspectprogrammer.myapp.IrreversibleSideEffects)")
public void idempotentOperation() {}

idempotentOperation是一个没有IrreversibleSideEffects注解的businessService。]

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章