[AOP] @AspectJ 语法概要

引言

这是继上篇《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();
    }
}

你可能感兴趣的:([AOP] @AspectJ 语法概要)