用开发时间方面提升生产力
一旦你习惯了给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)
|