AspectJ 传统语法概要

引言

Aspect Oriented Programming(AOP),作为Object Oriented Programming(OOP)的一种有益补充,已经成为解决现代企业应用(Enterprise Application)的一大利器,有效弥补了OOP在应对复杂需求变化时的无奈。借由AOP,我们可以在不改变业务领域模型的前提下,使用一种称为切面(Aspect)的元素,改变模型中各种类的结构与行为。其中,改变结构的方式被称为静态切入(Static Crosscutting),改变行为的方式被称为动态切入(Dynamic Crosscutting)。

AspectJ是一门基于Java的AOP语言,它通过为Java语言添附额外的关键字、语法结构等元素,实现了aspect的编译与织入(Weaving)。AspectJ 1.0诞生于2002年,经历了2006年前后的低潮期,在2008年伴随Spring框架的广泛应用,重新回到OOP的大舞台(据Ramniva Laddad称,AspectJ的发展由SpringSource赞助)。

尽管AspectJ基于Java实现,但不妨碍我们通过学习AspectJ掌握AOP的技巧。另一方面,已经有人尝试将AspectJ移植到.NET平台,出现了AspectJ.NET这样的工具。在.NET世界,除了移植的Spring.NET,还有同样开源的轻量级依赖注入框架Unity,后者在Microsoft的Enterprise Library中已经得到了广泛应用。需要指出的是,无论Spring亦或Unity,都只实现了完整AOP技术中的一小部分。

具体到AspectJ的使用,Eclipse配合JRE 1.6和AJDT(AspectJ Development Tools)是一个强大的组合,其他流行的IDE貌似尚无等效的插件支持传统的AspectJ语法。

本文正是我阅读《AspectJ in Action》、学习AspectJ的一个简单回顾,重点是AspectJ中关于aspect结构的描述。


AspectJ传统语法

AspectJ有两种方法描述和实现一个aspect。其中传统的方式,是使用aspect、pointcut等关键字和一些特定的语法结构。而另一种是注解Annotation的方式,其基础仍旧是传统的语法结构,只是为了更好地与Java语言本身以及Spring契合,这也非常类似于.NET下的Attribute。两种方式中,传统语法的切入方式更复杂和完善,而@AspectJ则为了与Java编译器妥协,主要支持动态切入。

// privileged表明是特权aspect,可以访问被切入目标的private成员
// abstract表明是抽象aspect,可以被继承
// 只有抽象aspect可以被继承实现,可以使用泛型参数
// 内嵌的aspect应定义为static的
public abstract privileged aspect CustomizeAspect extends BaseAspect
    // aspect与其切入目标之间的三类关联方式,其生命周期将与其关联者一致
    // 而在aspect中定义的advice的生命周期由aspect决定
    // singleton是默认的
    issingelton()
    // execution的this,call的target,类似于被切入类的实例对象成员
    // 通常用于事务管理的percflow
    perthis|pertarget|percflow|percflowbelow(onepointcut(para1, para2))
    // 类似于被切入类的static成员,模板支持通配符
    pertypewithin(onetype*)

    // 可以在aspect类型上使用static方法aspectOf()或者hasAspect()
    // 使用class、object或者无参数形式得到关联的aspect实例
    // 前者返回一个aspect的实例,因此可用作IoC框架中的工厂方法
    // 后者确定是否已经有关联的aspect实例
{
    // 同一jointpoint上若干aspect之间的优先级遵循如下原则(会产生一定程度耦合):
    // before -> around -> after,高优先级的总在这个链的最外侧(包括around本身)
    // 派生aspect总是优先于基aspect
    // 优先级越高,就越早before,越外层around,越晚after
    // 优先顺序从左至右,可定义在任何一个aspect中,要尽量避免循环引用和错链

    // 同一aspect内部的若干advice之间的优先级由定义顺序决定,越早定义优先等级越高    
    // 但是,before advice总在本体执行前执行,after advice总在本体执行后执行(即proceed()后)
    declare precedence: aspect1*, aspect2+, aspect3

    // 使用Inter-type方式为被切入类型添加成员,语法同class,只是要求以被切入类型名为前缀
    // 被切入类型必须是具体的,不能是抽象类,但可以是接口
    // aspect切入接口后,该接口的所有实现类的接口方法实现被切入
    // 接口的实现类中的接口方法将优先于aspect中的接口方法实现,不会被后者覆盖
    // public成员不能引入与被切入类中成员同名的,private成员则可以同名,Weaver会负责编译重命名
    public int JoinType._mixinField;
    public int JoinType.mixinMethod()    { /*...*/ }

    // 改变被切入类的继承结构,被继承者必须是具体的某个类型或者接口
    // 支持注解@Annotation与类型通配符
    declare parents: @OneAnnotation jointype* extends OneClass;
    declare parents: @OneAnnotation jointype* implements IOneInterface;

    // 所有包含被某个注解修饰的方法或字段的类型都作为匹配类型
    declare parents: hasmethod(@OneMethodAnnotation * * (*)) extends OneClass;
    declare parents: hasfield(@OneMethodAnnotation) extends AnotherClass    ;
    
    // 改变方法、构造子、字段和类型的注解
    declare @method: public * Account.*(..) : @Secured();
    declare @constructor: AccountService+.new() : @ConfigurationOnly;
    declare @field: * MissileInformation.* : @Classified;
    declare @type: banking..* : @PrivacyControlled;

    // 织入时(weave-time)的警告与错误提示
    declare warning: call (void OneClass.OneMethod()) : "This is an warning.";
    declare error: call (void OneClass.OneMethod()) : "This is an error.";

    // 软化异常:编译器不会要求在方法原型处声明要抛出的异常
    declare soft: Exceptiontype : call (void OneClass.OneMethod());    
    
    // 抽象pointcut,只定义名称与参数,joinpoint定义留待派生面中去实现
    abstract pointcut namedpointcut(Type1 para1, Type2 para2);

    // pointcut的定义,各种例子。语义为:选择某个JoinPoint,其满足xxx等条件。
    // 通配符:* 所有字符或参数类型,+ 以之结尾的及其派生类,.. 若干参数
    public pointcut executionPointcut(): execution(public returntype classname*.methodname+(..));

    // call-target会将aspect织入调用者,而execution-this则织入被调用者
    public pointcut cachedAccess(Account account, Object arg, Cachable cachable)
        : call(@Cachable * * (*)) throws OneExpcetion
        && target(account)        
        || args(arg)
        
        // 字段的setter与getter,读写访问
        && get(field)
        || set(field)

        // 触发异常时
        && handler(ExceptionType)        
        
        // 被某个注解所修饰
        && @annotation(cachable)
        
        // cflow针对整个从入口到出口的流程,cflowbelow只管入口进去之后到返回之前的流程,后者可避免递归
        || cflow(execution (* Account.debit(..)))
        || cflowbelow(execution (* Account.debit(..)))        
        
        // 以下对应对象的构造、从基类开始的预构造,类的构造(主要指static成员)    
        && initialization()
        && preinitialization()
        && staticinitialization()

        // withincode针对构造子或方法,within针对类型
        && within(Account)
        && !within(CustomizeAspect)
        && withincode(Account);
        
    // 三类advice,一个完整的定义包括3个部分:
    // 1. advice作用的时机before、around、after,返回的参数与传入的参数,将抛出的异常
    // 2. advice的作用点,由具体的pointcut决定
    // 3. advice的具体实现代码 
        
    // 可以通过target、this、args等关键字或者或者通过反射Reflection获得advice运行时的上下文
    // thisJoinPoint: 动态的内容,比如涉及的实例对象、参数、调用者对象
    // thisJoinPointStaticPart:连接点的静态内容,比如方法的原型、参数类型
    // thisEnclosingJoinPointStaticPart:与连接点相关的静态内容,比如包裹被切入方法的类型名    
    before(): namedpointcut() &&  call (* Account.*(..)) { /*..*/ }

    // 不影响切入目标的after,比如日志
    after(): namedpointcut() &&  call (* Account.*(..)) { /*..*/ }
    // 当目标正常执行并返回时的切入,返回类型将用作连接点匹配
    after() returning(Type retobject): namedpointcut() &&  call (* Account.*(..)) { /*..*/ }
    // 当目标触发某种异常时的切入,异常将用作连接点匹配
    after() throwing(ExceptionType ex): namedpointcut() &&  call (* Account.*(..)) { /*..*/ }
    
    // around的返回值,由被切入目标决定。若切入目标返回类型不一致,改用Object。AspectJ会自动装箱拆箱
    // around中checked的异常,要在两个地方声明,且它只负责advice自身触发的异常,proceed()触发的异常可以不用声明
    // around可以改变返回值
    void around(Account account, float amount) throws OneException
        : call(public void Account.dbit(float) throws OneException)
        && target(account)
        && args(amount)
        {
            /*..*/
            proceed(account, amount);
            /*..*/
        }    
}

参考链接


posted @ 2012-04-10 16:28  没头脑的老毕  阅读(1759)  评论(0编辑  收藏  举报