学习《spring 3.x企业应用开发实战》之基于@AspectJ和Schema的AOP

前言
  • 上一章中已经介绍,Spring中定义一个切面是比较麻烦的,需要实现专门的接口,并进行一些较为复杂的配置。经过改进,如今Spring AOP已经焕然一新,用户可以使用@AspectJ注解非常容易的定义一个切面,而不需要实现任何接口
  • 对于jdk5.0以下的项目,则可以通过基于Schema的配置定义切面

1、Spring对AOP的支持
spring2.0以后对AOP功能进行了重要的增强,主要变现在以下几个方面:
  • 新增了基于Schema的配置支持,为AOP提供了专门的aop命名空间;
  • 新增了对AspectJ切点表达式语言的支持,通过注解在POJO中定义切面;
  • 无缝集成AspectJ

2、JDK5.0注解知识
2.1、什么是注解
(1)注解是Javadoc标签和Xdoclet标签的延伸和发展,在java5.0以上版本,我们可以自定义这些标签,并通过java语言的反射机制获取类中标注的注解,完成特定的功能。
(2)注解是代码的附属信息,它遵循一个基本原则:注解不能直接干扰程序代码的运行,无论增加或删除注解,代码都能正常运行。

2.2、一个简单的注解类
(1)声明一个注解
@Retention(RetentionPolicy.RUNTIME)//①声明注解的保留期限
@Target(ElementType.METHOD)//②声明可以使用该注解的目标类型
public    @interface    TestInterface{//③定义注解
        boolean    value()    default    true;//④声明注解成员
}

(2)注解的成员声明有以下几点限制:
  • 成员以无入参、无抛出异常的方式声明,如int    value(String aa),boolean    value()    throws    Exception等都是非法的;
  • 可以通过default为成员指定一个默认值;
  • 成员类型是受限的,合法的类型包括原始类型及其封装类、String、Class、枚举类、注解类型、上述类型的数组类型;
  • 如果注解只有一个成员,则成员名必须取为value(),这样在使用时,可以忽略成员名和赋值等号(=);
  • 如果注解有多个成员,只对value赋值时可以省略value和赋值等号(=),多个成员赋值用逗号隔开;
  • 所有的注解都隐式继承于Annotation,但注解不允许显式继承其他接口;
  • 如果成员是数组类型,则使用“{xxx,xxx,xxx}”将值赋给成员名。

(3)jdk5.0以后,对于Package、Class、Constructor、Method、Field等反射对象都新增了访问注解信息的方法,该方法支持通过泛型直接返回注解对象。


3、着手使用@AspectJ
3.1、使用前的准备
因为java反射机制无法获取入参名,所以需要引入spring的asm模块,asm是轻量级的字节码处理框架,spring就利用asm处理@AspectJ中所描述的方法入参名。

3.2、一个简单的例子
@Aspect--------------//①通过该注解将类标识为一个切面
public    class    TestAspectj{
        @Before("execution(* greetTo(..))")-----------------------//②定义切点和增强类型
        public    void    beforeGreeting(){---------------------//③增强的横切逻辑
                ..............................
        }
}
注:②处的切点表达式的意思是,在greetTo方法处织入增强,greetTo方法可以有任意入参和返回值。

3.3、通过配置使用@Aspect切面
(1)如同spring使用proxyfactory将切面织入目标对象,AspectJ使用AspectJProxyFactory将切面织入目标对象。
(2)AspectJ的自动代理创建器是
<bean    class="org.springframework.aop.aspectj.annotation. AnnotationAwareAspectJAutoProxyCreator"/>
(3)抛开自动代理创建器,使用基于Schema的aop命名空间进行配置更简单:
<!-- 基于@Aspect切面的驱动器 -->
<aop:aspectj-autoproxy/>
<bean    id="target"    class="目标对象"/>
<bean    class="注解指定的切面类"/>
当然,spring内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现细节已经被<aop:aspectj-autoproxy/>隐藏起来了。
<aop:aspectj-autoproxy/>有一个属性proxy-target-class属性:
  • 默认为false,表示采用jdk的动态代理织入切面;
  • 值为true,表示采用cglib的动态代理织入增强;
  • 不过即使proxy-target-class属性设置为false,如果目标类没有声明接口,则spring将自动使用cglib动态代理。

3.4、@Aspect语法基础
(1) 切点表达式函数
切点表达式由关键字和操作参数组成,比如execution(* greetTo(..)),exccution就是关键字,* greetTo(..)就是操作参数。
spring支持9种@AspectJ切点表达式函数,根据描述对象的不同,可以分为4种类型:
  • 方法切点函数:通过描述目标类方法信息定义连接点;
  • 方法入参切点函数:通过描述目标类方法入参的信息定义连接点;
  • 目标类切点函数:通过描述目标类的代理类的信息定义连接点;
  • 代理类切点函数:通过描述目标类的代理类的信息定义连接点。
4种类型的切点函数如图:


(2) 在函数入参中使用通配符
@Aspect支持3种通配符:
  • *    匹配任意字符,但只能匹配上下文中的一个元素;
  • ..    匹配任意字符,可以匹配上下文中的多个元素,但在表示类时,必须和*联合使用,而在表示入参时,则单独使用;
  • +   表示按类型匹配指定类的所有类,必须跟在类名后面,如com.baobaotao.Car+。表示匹配Car类及其子类;
@AspectJ函数按其是否支持通配符及其支持的程度,可以分为以下3类:
  • 支持所有通配符:execution(),within();
  • 仅支持“+”通配符:args(),this(),target();
  • 不支持通配符:@args(),@within(),@target()和@annotation()。

(3) 逻辑运算符
切点表达式由切点函数组成,切点函数之间还可以进行逻辑运算,组成复合切点,
spring支持以下的切点运算符: &&,||,!
由于以上在XML中是特殊字符,spring在XML中的切点表达式函数提供 and,or,not作为替代。
注:当使用not时,前面得有空格,否则报解析错误。

(4) 不同的增强类型
@AspectJ为各种增强类型提供了不同的注解
@Before    相当于BeforeAdvice,它有2个成员:
  • value    该成员用于定义切点;
  • argNames    由于无法通过反射机制获取方法入参名,所以如果在Java编译时未启用调试信息或者需要在运行期解析切点,就必须通过这个成员指定注解所标注增强方法的参数名(注意两者名字必须完全相同),多个参数名用逗号分隔。
@AfterReturning    后置增强,相当于AfterReturningAdvice,AfterReturning,它拥有4个成员:
  • value    该成员用于定义切点;
  • pointcut    表示切点的信息,如果显示指定point值,它将覆盖value的设置值,可以将pointcut成员看成是value的同义词;
  • returning    将目标对象方法的返回值绑定给增强的方法;
  • argNames   如前所述。
@Around    环绕增强,相当于MethodInterceptor,它有2个成员:
  • value    该成员用于定义切点;
  • argNames   如前所述。
@AfterThrowing    抛出增强,相当于ThrowsAdvice,AfterThrowing,它有4个成员:
  • value    该成员用于定义切点;
  • pointcut    表示切点的信息,如果显示指定point值,它将覆盖value的设置值,可以将pointcut成员看成是value的同义词;
  • throwing    将抛出的异常绑定到增强方法中;
  • argNames   如前所述。
@After    Final增强,不管是抛出异常还是正常退出,该增强都会得到执行,该增强没有对应的增强接口,可以把它看成ThrowsAdvice和AfterReturningAdvice的混合物,一般用于释放资源,它有2个成员:
  • value    该成员用于定义切点;
  • argNames   如前所述。
@DeclareParents    引介增强,相当于IntroductionInterceptor,它有2个成员:
  • value    该成员用于定义切点,它表示在哪个目标类上添加引介增强,即不需要使用函数表达式,直接指定全类名
  • defaultImpl    默认的接口实现类。

(5) 引介增强的具体用法



引介增强通过切面技术将目标类要实现接口的子类融合到目标类中,目标类持有目标接口的实现类的引用,这样目标类就相当于实现了目标接口。

3.5、切点函数详解
(1) execution()
execution()是最常用的切点函数,其语法如下所示:
execution(<访问修饰符>?<返回类型><方法名>(<方法参数>)<异常>?)
?:表示前面的内容可选,即除了返回类型,方法名和方法参数,其他都是可选的。
具体示例:



(2) args()和@args()




(3) 命名切点
在前面的例子中,切点直接声明在增强方法处,这种切点声明方式称为 匿名切点,匿名切点只能在声明处使用。如果希望其他地方 重用一个切点,我们可以通过 @Ponitcut注解以及 切面类方法对切点对切点进行命名。
具体示例如下:




(4) 增强织入的顺序
一个连接点可以同时匹配多个切点,切点对应的增强在连接点上的织入顺序是如何安排的呢?
这个问题要分3种情况讨论:
  • 如果增强在同一个切面类中声明,则依照增强在切面类中定义的顺序进行织入;
  • 如果增强位于不同的切面类中,且这些切面类都实现了org.springframework.core.Ordered接口,则顺序号小的先织入;
  • 如果增强位于不同的切面类中,且这些切面没有实现Ordered接口,则织入的顺序是不确定的。

(5) 访问连接点信息
AspectJ使用org.aspectj.lang.JoinPonit接口表示目标类连接点对象,如果是环绕增强时,使用org.aspectj.lang.ProceedingJoinPoint表示连接点对象,该类是JoinPoint的子接口。 任何一个增强方法都可以通过将第一个入参声明为JoinPoint访问到连接点上下文的信息(一定要在第一个位置)。
JoinPonit和ProceedingJoinPoint的方法签名如下:


(6) 绑定连接点方法入参



(7) 绑定代理对象


(8) 绑定返回值



3.6、基于Schema配置切面
(1)基于Schema的配置就是将切面信息移到XML中, 一个配置示例如下



(2) 配置命名切点:
在7-15②处通过pointcut属性声明的切点是匿名切点,它不能被其他增强或其他切面引用。spring提供了命名切点的配置方式,如下所示:



(3) 各种增强类型的配置







(4) 绑定连接点信息



(5) Advisor配置
一个配置示例:



3.7、混合切面类型
(1) 配置示例:



(2) 切面类型总结:




4、JVM Class文件字节码转换基础知识
在类加载期织入切面。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值