引言
这是继上篇《AspectJ传统语法概要》之后,对@AspectJ语法的一个整理。
传统的AspectJ固然强大,但需要特定的编译器ajc支持。为了避免这样的依赖,以“Keeping the code plain Java”的方式去实现AOP,AspectJ提供了一种略微区别于传统语法的形式:@AspectJ。@AspectJ使用了Annotation作为实现aspect各要素的基础,使实现aspect的代码可以被任意的Java编译器理解并编译运行。尽管@AspectJ相比传统的AspectJ语法更加冗长和累赘,但却更容易被普通的Java程序员理解和使用。流行的框架Spring,也使用了从@AspectJ衍生的AOP实现方式。
需要注意的是,正是为了兼容普通的Java编译器,@AspectJ做出了相当大的妥协,只实现了AspectJ的一个子集:aspect、pointcut、advice、declaring parents与declaring errors and warnings,而introducing、exception handling与privileged aspect等内容没有得到支持。掌握了AspectJ的传统语法,理解@AspectJ就会变得非常简单了。
@AspectJ下的aspect实现
// @Aspect不再能修饰接口,而只能是类 // 访问aspect实例时,不再能使用aspectOf()和hasAspect() // 而应以aspect的类作为参数,使用由org.aspectj.lang.Aspects提供的静态方法aspectOf()与hasAspect() @Aspect("perthis|pertarget|percflow|percflowbelow(Pointcut) | pertypewithin(TypePattern)") // 定义aspect的优先顺序,需要使用完全的限定名,这在@AspectJ中很普遍,也是由Java编译器决定的 // AspectJ的未来版本可能提供string[]类型的参数支持 @DeclarePrecedence("ajia.HomeSecurityAspect, ajia.SaveEnergyAspect") public abstract static class AspectName extends class_or_aspect_name implements interface_list { // 使用@Pointcut配合一个占位用的方法声明来定义一个pointcut // 抽象pointcut依旧只有名字、参数,没有实际的joinpoint定义 @Pointcut public abstract void pointcut_name(Type args); // pointcut定义时仍要注意使用全限定名 // 方法只是占位符,方法体除了采用类似条件编译时的if()切入方式外都置空 // 若方法会抛出异常,则同样要在方法原型加上throws声明 // 切记要开启编译器选项-g:vars,让编译器预留参数名(建设采用这种方式) @Pointcut("execution(public * ajia.banking.domain.Account.*(float)) && this(account) && args(amount)") public void accountOperation(Account account, float amount) {} // 或者利用Annotation的属性,建立参数名与pointcut之间的关联 // 但这样得自己维护argNames与方法参数表的一致性,所以不推荐 @Pointcut(value="execution(public * ajia.banking.domain.Account.*(float)) && this(account) && args(amount)", argNames="account, amount") public void accountOperation(Account account, float amount) {} // advice的定义类似传统语法, // before-advice必须是public与void的 // 方式一:匿名pointcut @Before("execution(* *(..)) && !within(ajia.monitoring.*)") public void beatHeart() { heartBeatListener.beat(); } // 方式二:命名的pointcut @Pointcut("execution(* *.*(..)) && !within(ajia.monitoring.*)") public void aliveOperation() {} @Before("aliveOperation()") public void beatHeart() { heartBeatListener.beat(); } // advice仍旧支持经由类JoinPoint的反射获取上下文 // JoinPoint对象本身定义动态部分 // JoinPoint.StaticPart定义静态部分 // JoinPoint.EnclosingStaticpart定义包裹静态信息的部分 // 同时,advice仍旧支持target/this/args @Pointcut("call(void Account.credit(float)) && target(account) && args(amount)") public void creditOperation(Account account, float amount) {} @Before("creditOperation(account, amount)" ) public void beforeCreditOperation(JoinPoint.StaticPart jpsp, JoinPoint.EnclosingStaticPart jpesp, Account account, float amount) { System.out.println("Crediting " + amount + " to " + account); } // after-advice的实现同样直观 @Pointcut("call(* java.sql.Connection.*(..)) && target(connection)") public void connectionOperation(Connection connection) {} @After("connectionOperation(connection)") public void monitorUse(Connection connection) { System.out.println("Just used " + connection); } @AfterReturning(value="connectionOperation(connection)", returning="ret") public void monitorSuccessfulUse(Connection connection, Object ret) { System.out.println("Just used " + connection + " successfully which returned " + ret); } @AfterThrowing(value="connectionOperation(connection)", throwing="ex") public void monitorFailedUse(Connection connection, Exception ex) { System.out.println("Just used " + connection + " but met with a failure of kind " + ex); } // around-advice的实现稍显复杂 // 需要参考JoinPoint反射的方式,为around-advice的方法传入一个ProceedingJoinPoint参数 // 该对象有方法proceed()及其重载版本proceed(Object[]),可以执行被切入的方法 // 这个Object[]数组中,依次为this-target-args // 分别经由ProceedingJoinPoint对象的方法this()、target()与getArgs()获取 @Around("pointcut_xxx()") public Object measureTime(ProceedingJoinPoint pjp) { Object[] context = formProceedArguments(pjp.this(), pjp.target(), pjp.getArgs()); Object result = proceed(context); return result; } // 可以用下面这个方法获取该Object[]数组 public static Object[] formProceedArguments(Object thiz, Object target, Object[] arguments) { int argumentsOffset = 0; if(thiz != null) { argumentsOffset++; } if(target != null) { argumentsOffset++; } Object[] jpContext = new Object[arguments.length + argumentsOffset]; int currentIndex = 0; if(thiz != null) { jpContext[currentIndex++] = thiz; } if(target != null) { jpContext[currentIndex++] = target; } System.arraycopy(arguments, 0,jpContext, argumentsOffset, arguments.length); return jpContext; } // 声明Error与Warning @DeclareError("callToUnsafeCode()") static final String unsafeCodeUsageError = "This third-party code is known to result in a crash"; @DeclareWarning("callToBlockingOperations()") static final String blockingCallFromAWTWarning = "Please ensure you are not calling this from the AWT thread"; // @AspectJ提供了@DeclareParents,但很少使用,而更多使用下面的@DeclareMixin作为替代 // @AspectJ实现的Mix-in,本质是一个返回proxy对象的工厂方法,用于返回一个包裹了aspect的proxy // 下面的代码等价于:declare parents: ajia.banking.domain.* implements Serializable; // 由于返回null,因此只能作为对被切入类的都有Serializable的一个标记 @DeclareMixin("ajia.banking.domain.*") public Serializable serializableMixin() { return null; } // @DeclareMixin支持一个参数,模仿依赖注入的方式,把被切入的对象传递给工厂 // AuditorImp是接口Auditor的一个实现,即对Object的一个代理 @DeclareMixin("ajia.banking.domain.*") public Auditor auditorMixin(Object mixedIn) { return new AuditorImpl(mixedIn); } // 要mix-in若干个接口,则需要在interfaces里依次放上要添附的接口 @DeclareMixin(value="ajia.banking.domain.*", interfaces="{Auditor.class, MonitoringAgent.class}") public AuditorMonitoringAgent mixin() { return new AuditorMonitoringAgentImpl(); } }