• 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);
                /*..*/
            }    
    }

    参考链接


    转载请注明出处及作者,谢谢!
  • 相关阅读:
    20172021年福建省普通高校分数线,招生报考及录取统计(理科)
    SUSE Linux Enterprise 15 SP1 系统安装
    常见的网络服务器软件综合比较介绍(apache、IIS、tomcat、jboss、resin、weblogic、websphere)
    车载继电器工作原理
    java线程池使用实例
    dubbo入门教程:基于dubbo实现服务之前调用
    Sentinal
    微服务之服务网关
    ElasticSearch查询(二)
    微服务之配置管理中心
  • 原文地址:https://www.cnblogs.com/Abbey/p/2440191.html
Copyright © 2020-2023  润新知