科技行者

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

知识库

知识库 安全导航

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

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

  • 扫一扫
    分享文章到微信

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

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

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

关键字:

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

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

用开发时间方面提升生产力

一旦你习惯了给Spring AOP编写@AspectJ方面,就会从AspectJ中获得额外的益处,即使你只在开发期间使用它(并且在你正在运行的应用程序中没有AspectJ编译的方面)。方面可以用来针对测试(它们使得某些模拟和错误注入变得更加容易)、调试和诊断问题,以及确保为你的应用程序所设计的设计指导方针得到实施。首先,让我们看一个设计实施方面(enforcement aspects)的实例。继续在数据访问层中进行,我们现在要引入Spring HibernateTemplate,让Spring替我们管理Hibernate会话,而不用我们自己管理。以下这个方面将确保程序员不会忘记开始管理他们自己的会话:

@public aspect SpringHibernateUsageGuidelines {
pointcut sessionCreation()
: call(* SessionFactory.openSession(..));
pointcut sessionOrFactoryClose()
: call(* SessionFactory.close(..)) ||
call(* Session.close(..));
declare error
: sessionCreation() || sessionOrFactoryClose()
: "Spring manages Hibernate sessions for you, " +
"do not try to do it programmatically";
}

有了这个方面之后,如果一位程序员在给Eclipse使用AspectJ Development Tools(AJDT)插件,他或者她就将在问题视图中看到一个编译错误的标识,并在源代码中出错的位置(与任何一般的编译错误完全一样)会有错误文本:“Spring替你管理Hibernate会话,请不要试图编程式地进行管理”(Spring manages Hibernate sessions for you, do not try to do it programmatically)。建议引入像这样的实施方面的方法是,将AspectJ编译步骤增加到用实施方面“织入”应用程序的构建过程——如果被方面发现构建错误,这项任务将会失败。

现在让我们看一下简单的诊断方面(diagnosis aspect)。回顾一下我们曾将一些事务标识为只读(一项很重要的性能优化)。随着应用程序复杂性的增加,从概念上来说,从事务划分所发生的服务层操作的位置,到作为指定用例的一部分而执行的业务领域逻辑,这之间可能十分遥远。如果在一个只读的事务期间,领域逻辑更新了一个领域对象的状态,我们就会有丢失更新的风险(从来没有提交到数据库)。这可能成为那些莫名其妙bug的根源。

LostUpdateDetector方面可以在开发时间用来侦测可能的丢失更新。

public aspect LostUpdateDetector {
private Log log = LogFactory.getLog(LostUpdateDetector.class);
pointcut readOnlyTransaction(Transactional txAnn) :
SystemArchitecture.businessService() &&
@annotation(txAnn) && if(txAnn.readOnly());
pointcut domainObjectStateChange() :
set(!transient * *) &&
SystemArchitecture.inDomainModel();
..

我已经通过在方面中定义两个有用的切入点开始了。readOnlyTransaction是有着@Transactional注解的businessService()的执行,readOnly()属性设置为true。domainObjectStateChange是任何非瞬时领域inDomainModel()的更新。(注意,这是进行了简化,但是对于组成一个领域对象状态变化的东西仍然很有用——我们可以将该方面扩展为处理集合等等,如果我们希望如此的话)。利用所定义的这两个概念,我们现在就可以通过potentialLostUpdate()表达想说的话了:

pointcut potentialLostUpdate() :
domainObjectStateChange() &&
cflow(readOnlyTransaction(Transactional));

potentialLostUpdate是在一个readOnlyTransaction(期间)的控制流中所做的一个domainObjectState变化。你从这里可以领略到切入点语言生效的威力。通过组成两个具名的切入点表达式,我们已经能够非常简单地表达一个很强大的概念。与你只有一个粗糙的拦截模型可用时相比,利用切入点语言更容易表达像potentialLostUpdate这样的条件。它也比像EJB 3所提供的那些过于单纯的拦截机制要强大得多。

最后,当发生potentialLostUpdate时,我们当然需要真正地做一些事情:

after() returning : potentialLostUpdate() {
logLostUpdate(thisJoinPoint);
}
private void logLostUpdate(JoinPoint jp) {
String fieldName = jp.getSignature().getName();
String domainType = jp.getSignature().getDeclaringTypeName();
String newValue = jp.getArgs()[0].toString();
Throwable t = new Throwable("potential lost update");
t.fillInStackTrace();
log.warn("Field [" + fieldName + "] in type [" + domainType + "] " +
"was updated to value [" + newValue + "] in a read-only " +
"transaction, update will be lost.",t);
}
}

以下是有了这个方面之后,运行一个测试案例所得到的日志信息:

[org.aspectprogrammer.myapp.domain.Pet] was updated to value [Mr.D.]
in a read-only transaction, update will be lost.
java.lang.Throwable: potential lost update
at org.aspectprogrammer.myapp.debug.LostUpdateDetector.logLostUpdate(LostUpdateDetector.aj:40)
at org.aspectprogrammer.myapp.debug.LostUpdateDetector.afterReturning(LostUpdateDetector.aj:32)
at org.aspectprogrammer.myapp.domain.Pet.setName(Pet.java:32)
at org.aspectprogrammer.myapp.service.impl.PetServiceImpl
.updateName(PetServiceImpl.java:40)
at org.springframework.transaction.interceptor.TransactionInterceptor
.invoke(TransactionInterceptor.java:100)
at org.aspectprogrammer.myapp.service.impl.ConcurrentOperationExecutor
.doConcurrentOperation(ConcurrentOperationExecutor.java:37)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:478)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:344)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)

作为题外话,解释一下干净且易读的堆栈轨迹(和适当的乐观重试逻辑)。易读的堆栈轨迹(stack trace)是由于从异常堆栈轨迹项中去除了干扰的另一个方面。没有适当的堆栈轨迹管理方面,所有的Spring AOP拦截堆栈框也都被显示出来,出现了像下面所示这样的堆栈轨迹。我想,你会认同说简化版是一个很大的改进!


[org.aspectprogrammer.myapp.domain.Pet] was updated to value [Mr.D.]
in a read-only transaction, update will be lost.
java.lang.Throwable: potential lost update
at org.aspectprogrammer.myapp.debug.LostUpdateDetector.logLostUpdate(LostUpdateDetector.aj:40)
at org.aspectprogrammer.myapp.debug.LostUpdateDetector.ajc$afterReturning

$org_aspectprogrammer_myapp_debug_LostUpdateDetector$1$b5d4ce0c(LostUpdateDetector.aj:32)
at org.aspectprogrammer.myapp.domain.Pet.setName(Pet.java:32)
at org.aspectprogrammer.myapp.service.impl.PetServiceImpl.updateName(PetServiceImpl.java:40)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:287)
at org.springframework.aop.framework.ReflectiveMethodInvocation

.invokeJoinpoint(ReflectiveMethodInvocation.java:181)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:148)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:100)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:170)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint

.proceed(MethodInvocationProceedingJoinPoint.java:71)
at org.aspectprogrammer.myapp.service.impl.ConcurrentOperationExecutor

.doConcurrentOperation(ConcurrentOperationExecutor.java:37)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.aop.aspectj.AbstractAspectJAdvice

.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:568)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:558)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:57)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:170)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:170)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:176)
at $Proxy8.updateName(Unknown Source)
at org.aspectprogrammer.myapp.debug.LostUpdateDetectorTests

.testLostUpdateInReadOnly(LostUpdateDetectorTests.java:23)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at junit.framework.TestCase.runTest(TestCase.java:154)
at junit.framework.TestCase.runBare(TestCase.java:127)
at junit.framework.TestResult$1.protect(TestResult.java:106)
at junit.framework.TestResult.runProtected(TestResult.java:124)
at junit.framework.TestResult.run(TestResult.java:109)
at junit.framework.TestCase.run(TestCase.java:118)
at junit.framework.TestSuite.runTest(TestSuite.java:208)
at junit.framework.TestSuite.run(TestSuite.java:203)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:478)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:344)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)

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

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

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