AspectJ 对 AOP 的实现
对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。然而,AspectJ 也实现了AOP的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以,Spring 又将 AspectJ 的对于 AOP 的实现也引入到了自己的框架中。
在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式。
- AspectJ 简介
AspectJ 是一个面向切面的框架,它扩展了 Java 语言。AspectJ 定义了 AOP 语法,它有一个专门的编译器用来生成遵守 Java 字节编码规范的 Class 文件。
—— 百度百科《AspectJ》
- AspectJ 的通知类型
AspectJ 中常用的通知有五种类型:
(1)前置通知
(2)后置通知
(3)环绕通知
(4)异常通知
(5)最终通知
其中最终通知是指,无论程序执行是否正常,该通知都会执行。类似于 try...catch 中的 finally 代码块。
- AspectJ 的切入点表达式
AspectJ 除了提供了六种通知外,还定义了专门的表达式用于指定切入点。表达式的原型是:execution ( [modifiers-pattern]访 问权限类型 ret-type-pattern返 回值类型 [declaring- type-pattern]全限定性类 名 name-pattern(param-pattern)方法名(参数名) [throws- pattern]抛出 异常类型 )
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:
举例: .
execution(public * *(.)))
指定切入点为:任意公共方法。
execution(* set *<..)))
指定切入点为:任何一个以“set"开始的方法。
execution(* com.xyz.service.*.*..))
指定切入点为:定义在service包里的任意类的任意方法。
execution(* com.xyz.servic..*<.)
指定切入点为:定义在service包或者子包里的任意类的任意方法。“."出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。
execution(* * .service.*.*(.)
指定只有一级包下的serivce子包下所有类(接口)中所有方法为切入点
execution(* *. .service.*.*(.)
指定所有包下的serivce子包下所有类(接中)中所有方法为切入点
execution(* *.ISomeService/*(.))
指定只有一级包下的ISomeSerivce接口中所有方法为切入点
execution(* *..ISomeService.*(.)
指定所有包下的ISomeSeriyce接口中所有方法为切入点
executlion(*com.xyz.service/lAccountService.<.))
指定切入点为: IAccountService 接口中的任意方法。
execution(* com.xyz.service.IAccountService+.*.))
指定切入点为: IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。
execution(* joke(String,int))
指定切入点为:所有的joke(String,int)方法,且 joke() 方法的第一个参数是 String,第二个参数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 joke( java.til.List, int)。
execution(* joke(String,*)))
指定切入点为:所有的joke()方法,该方法第-一个参数为String, 第二个参数可以是任意类型,如 joke(String s1,String s2) 和 joke(String s1,double d2) 都是,但 joke(String s1,double d2 ,String s3)不是。
execution(* j<e(rin,..))
指定切入点为:所有的joke()方法,该方法第一个参数为String,后面可以有任意个参数且参数类型不限,如joke(String s1)、joke(String s1,String s2)和joke(String s1,double d2,String s3)都是。
execution(* joke(Object))
指定切入点为:所有的joke()方法,方法拥有-一个参数,且参数是Object类型。joke(Object ob)是,但,joke(String s)与joke(User u)均不是。
execution(* joke(Object+))
指定切入点为:所有的joke()方法,方法拥有一一个参数,且参数是Object类型或该类的子类。不仅joke(Object ob)是,joke(String s)和 joke(User u)也是。
AspectJ 的开发环境
(1)导入两个 Jar 包
AspectJ 是专门针对 AOP 问题的,所以其运行是需要 AOP环境的,即需要之前的 AOP 的两个Jar 包。另外,还需要AspectJ 自身的 Jar 包:在 Spring 支持库解压目录中的子目录 org.aspectj下有两个子包:
一般情况下,使用 weaver 包中的 Jar 即可。tools 中的 Jar 除了包含 weaver 中类库外,还包含了其它工具,但一般不用。所以,使用 weaver 包中的 Jar 即可。
当然,在 Spring 中使用 AspectJ,还需要将它们联系一起的整合 Jar 包。在Spring框架解压目录的 libs 中。
(2)引入 AOP 约束
在配置文件头部,要引入关于 aop 的约束。在 Spring 框架的解压目录中,\docs\spring-framework-reference\html下的xsd-configuration.html文件中。
在前面 Spring 实现 AOP 时,并未引入 AOP 的约束,而在 Aspect 实现 AOP 时,才提出要引入 AOP 的约束。说明,配置文件中使用的 AOP 约束中的标签,均是 AspectJ 框架使用的,而非 Spring 框架本身在实现 AOP 时使用的。
AspectJ 对于 AOP 的实现有两种方式:
(1)注解方式。
(2)XML方式。
- AspectJ 基于注解的 AOP 实现。
(1)实现步骤
Step1:
定义业务接口与实现类
Step2:
定义切面 POJO 类
该类为一个 POJO 类,将作为切面出现。其中定义了若干普通方法,将作为不同的通知方法。
Step3:
在切面类上添加 @Aspect 注解
在定义的 POJO 类上添加 @Aspect 注解,指定当前 POJO 类将作为切面。
Step4:
在 POJO 类的普通方法上添加通知注解
切面类是用于定义增强代码的,即用于定义增强目标类中目标方法的增强方法。这些增强方法使用不同的“通知”注解,会在不同的时间点完成织入。当然,对于增强代码,还要通过 execution 表达式指定具体应用的目标类与目标方法,即切入点。
Step5:
注册目标对象与 POJO 切面类
Step6:
注册 AspectJ 的自动代理
在定义好切面 Aspect 后,需要通知 Spring 容器,让容器生成“目标类+切面”的代理对象。这个代理是由容器自动生成的。只需要在 Spring 配置文件中注册一个基于 aspectj 的自动代理生成器,其就会自动扫描到 @Aspect 注解,并按通知类型与切入点,将其织入,并生成代理。
<aop:aspectj-autoproxy/>的底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的。从其类名就可看出,是基于 AspectJ 的注解适配自动代理生成器。
其工作原理是,<aop:aspectj-autoproy/> 通过扫描找到 @Aspect 定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
Step7:
测试类中使用目标对象的 id
举例:Spring_AspectJ 项目的 annotation 包。
(2)@Before 前置通知 - 方法有 JoinPoint 参数
在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个JoinPoint类型参数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象等。
不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数。
(3)@AfterReturning 后置通知 - 注解有 returning 属性
在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
(4)@Around 环绕通知 - 增强方法有 ProceedingJoinPoint 参数
在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个 proceed() 方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
(5)@AfterThrowing 异常通知 - 注解中有 throwing 属性
在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。
(6)@After 最终通知
无论目标方法是否抛出异常,该增强均会执行。
(7)@Pointcut 定义切入点
当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。
其用法是,将 @Pointcut 注解在一个方法之上,以后所有的 executeion 的 value 属性值均可使用该方法名作为切入点。代表的就是 @Pointcut 定义的切入点。这个使用 @Pointcute 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。
- AspectJ 基于 XML 的 AOP 实现
AspectJ 除了提供了基于注解的 AOP 的实现外,还提供了以 XML 方式的实现。
切面就是一个 POJO 类,而用于增强的方法就是普通的方法。通过配置文件,将切面中的功能增强织入到了目标类的目标方法中。
(1)实现步骤
Step1:
定义业务接口与实现类
Step2:
定义切面 POJO 类
该类为一个 POJO 类,将作为切面出现。其中定义了若干普通方法,将作为不同的通知方法。
Step3:
注册目标对象与 POJO 切面类
Step4:
在容器中定义 AOP 配置
配置文件中,除了要定义目标类与切面的 Bean 外,最主要的是在<aop:config/>中进行 aop 的配置。而该标签的底层,会根据其子标签的配置,生成自动代理。
通过其子标签<aop:pointcut/>定义切入点,该标签有两个属性,id 与 expression 。 分别用于指定该切入点的名称及切入点的值。expression 的值为 execution 表达式。
通过子标签<aop:aspect/>定义具体的织入规则:根据不同的通知类型,确定不同的织入时间,将 method 指定的增强方法,按照指定织入时间,织入到切入点指定的目标方法中。
<aop:aspect/>的 ref 属性用于指定使用哪个切面。
<aop:aspect/>的子标签是各种不同的通知类型。不同的通知所包含的属性是不同的,但也有共同的属性。
method:
指定该通知使用的切面中的增强方法。
pointcut-ref:
指定该通知要应用的切入点。
Aspectl 的 6 种通知的 XML 标签如下:
① <aop:before/>:前置通知
② <aop:after-returning/>:后 置通知
③ <aop:around/>:环绕通知
④ <aop:after- throwing/>:异常通知
⑤ <aop:after/>“”最终通知
⑥ <aop:declare- parents/>:引入通知
Step5:
测试类中使用目标对象的 id
举例:Spring_AOP_AspectJ 项目的 xml 包。
(2)<aop:bofore/>前置通知
选择重载的方法:在 method 属性赋值时,不仅要放方法名,还要放入方法的参数类型全类名。
(3)<aop:after-returning/>后置通知
其 XML 的配置中,有一个属性 returning,指定用于接收目标方法的返回值所使用的变量名。其可作为增强方法的参数出现。
(4)<aop:around/>环绕通知
环绕通知的增强方法一般返回类型为 Object,是目标方法的返回值。并且可以包含一个参数 ProceedingJoinPoint,其方法 proceed() 可执行目标方法。
(5)<aop:after-throwing/> 异常通知
其 XML 的配置中,有一个属性 throwing,指定用于接收目标方法所抛出异常的变量名。其可作为增强方法的参数出现,该参数为 Throwable 类型。
(6)<aop:after/>最终通知
网友评论