1.Spring 对 AOP 的支持
新增了基于 Schema 的配置支持, 为 AOP 专门提供了 aop 命名空间
新增了对 AspectJ 切点表达式语言的支持,
可以无缝地集成 AspectJ
2.Java5.0 注解知识快速进阶
注解是代码的附属信息, 它遵循一个基本原则: 注解不能干扰程序代码的运行, 无论增加或删除注解, 代码都能够正常运行. Java 语言解释器会忽略这些注解, 而由第三方工具负责对注解进行处理. 第三方工具可以利用代码中的注解间接控制程序代码的运行, 它们通过 Java 反射机制读取注解的信息, 并根据这些信息更改目标程序的逻辑, 而这正是 Spring AOP 对 @AspectJ 提供支持所采取的办法.
AspectJ 实例的 github 地址 https://github.com/FreshSmall/SpringDemo
3. 使用 @AspectJ
@AspectJ 采用注解来描述切点和增强
- @Aspect// 通过该注解将 PreGreetingAspectJ 标识为一个切面
- public class PreGreetingAspectJ {@Before("execution( * *To(..))")// 定义切点和增强类型
- public void before() throws Throwable {// 增强的横切逻辑
- System.out.println("How are you! Mr");
- }
- }
- <?xml version="1.0" encoding="UTF-8"?>
- <!-- Configures the Camel Context-->
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
- <bean id="waiter" class="com.example.NativeWaiter"/>
- <bean class="com.example.beforeapsectJ.PreGreetingAspectJ"/>
- <!-- 方法一, 自动代理创建器, 自动将 @AspectJ 注解切面类织入目标 Bean 中 -->
- <!--<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>-->
- <!-- 方法二, 上面方法使用的是 Annotation AspectJAutoProxy Creator 能够将 @AspectJ 注解切面类自动织入目标 Bean 中, 如果使用的是基于 Schema 的 aop
- 命名空间进行配置, 可以使用如下方法进行配置 -->
- <aop:aspectj-autoproxy/>
- </beans>
4.@AspectJ 语法基础
4.1. 切点表达式函数
方法切点函数: 通过描述目标类方法的信息定义连接点
方法入参切点函数: 通过描述目标类方法入参的信息定义连接点
目标类切点函数: 通过描述目标类类型的信息定义连接点
代理类切点函数: 通过描述目标类的代理类的信息定义连接点
4.2. 在函数入参中使用通配符
: 匹配任意字符, 但它只能匹配上下文中的一个元素
.. : 匹配任意字符, 可以匹配上下文中的多个元素, 但在表示类时, 必须和 * 联合使用, 而在表示入参时则单独使用
: 表示按类型匹配指定类的所有类, 必须跟在类后面, 如 com.smart.Car+. 继承或扩展指定类的所有类, 同时还包括指定类本身
4.3. 逻辑运算符
&& : 与操作符, 相当于切点的交集运算
|| : 或操作符, 相当于切点的并集运算
! : 非操作符, 相当于切点的反集运算
4.4. 不同增强类型
@Before, 前置增强, 相当于 BeforeAdvice,Before 注解类拥有两个成员
value: 该成员用于定义切点
argNames: 由于无法通过 Java 反射机制获取方法入参名, 所以如果在 Java 编译时未启用调试信息, 或者需要在运行期解析切点, 就必须通过这个成员指定注解所标注增强方法的参数名(注意二者名字必须完全相同), 多个参数名用逗号分隔
@AfterReturning, 后置增强, 相当于 AfterReturningAdvice,AfterReturning 注解类拥有 4 个成员
value: 该成员用于定义切点
pointcut: 表示切点的信息, 如果显示指定 pointcut 值, 那么它将覆盖 value 的设置值, 可以将 pointcut 成员看做 value 的同义词
returning: 将目标对象方法的返回值绑定给增强的方法
argNames: 如上面所述(参数名)
@Around, 环绕增强, 相当于 MethodInterceptor,Around 注解类拥有两个成员
value: 该成员用于定义切点
argNames: 如上面所述(参数名)
@AfterThrowing, 抛出增强, 相当于 ThrowsAdvice,AfterThrowing 注解类拥有 4 个成员
value: 该成员用于定义切点
pointcut: 表示切点的信息, 如果显示指定 pointcut 值, 那么它将覆盖 value 的设置值. 可以将 pointcut 成员看作 value 的同义词.
throwing: 将抛出的异常绑定到增强的方法中
argNames: 如上面所述(参数名)
@After,Final 增强, 不管是跑出异常还是正常退出, 该增强都会得到执行, 该增强没有对应的增强接口, 可以将它看成 ThrowsAdvice 和 AfterReturningAdvice 的混合物, 一般用于释放资源, 相当于 try{}finally{}的控制流. After 注解类拥有两个成员
value: 该成员用于定义切点
argNames: 如上面所述(参数名)
@DeclareParents, 引介增强, 相当于 IntroductionInterceptor,DeclareParents 注解类拥有两个成员
value: 该成员用于定义切点, 它表示在哪个目标类上添加引介增强
defaultImpl: 默认的接口实现类
5. 切点函数详解
5.1.@annotation(): 表示标注了注解的所有方法
- @AspectJ
- public class TestAspectJ {
- @AfterReturning("@annotation(com.example.annotationAspectJ.Override)")// 后置增强切面
- public void needTestFun(){
- System.out.println("needTestFun() executed!");
- }
- }
5.2.execution(): 其语法如下
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
通过方法签名定义切点
execution(public (..)): 匹配所有的目标类的 public 方法, 第一个代表返回
类型, 第二个代表方法名
execution(* TO(..)): 匹配目标类所有以 To 为后缀的方法, 第一个代表返回类
型, 而 * To 代表任意以 To 为后缀的方法
通过类定义切点
execution(* com.example.Waiter.(..)): 匹配 Waiter 接口的所有方法, 第一个代表返回任意类型, com.example.Waiter.* 代表接口中的所有方法
execution(* com.example.Waiter+.*): 匹配 Waiter 接口及其所有实现类的方法,
通过类包定义切点
execution(* com.example.*(..)): 匹配 com.example 包下所有类的所有方法
execution(* com.example..*(..)): 匹配 com.example 包, 子孙包下所有类的所有方法
execution(* com...Dao.finf*(..)): 匹配包名前缀为 com 的任何包下类名后缀为 Dao 的方法, 方法名必须以 find 为前缀
通过方法入参定义切点
execution(* joke(String,int)): 匹配 joke(String,int)方法, 且 joke()方法的第一个入参是 String, 第二个入参是 int, 如果方法中入参类型是 java.lang 下的包可以直接使用类名; 否则必须使用全限定类名
execution(* joke(String,*)): 匹配目标类中的 joke()方法, 该方法第一个入参为 String, 第二个入参可以是任意类型
execution(* joke(String,..)): 匹配目标类中的 joke()方法, 该方法的第一个入参为 String, 后面可以有任意个入参且入参类型不限
execution(* joke(Object+)): 匹配目标类中的 joke()方法, 方法拥有一个入参, 且入参是 Object 类型或该类的子类
5.3.args()和 @args()
args()函数入参是类名, 而 @args()函数的入参必须是注解类的类名. 虽然 args()允许在类名后使用 "+" 通配符, 但该通配符在此处没有意义, 添加和不添加效果都一样
args(): 该函数接收一个类名, 表示目标类方法入参对象是指定类 (包含子类) 时, 切点匹配
@args(): 该函数接收一个注解类的类名, 当方法的运行时入参对象标注了指定的注解时, 匹配切点
5.4.within()
通过类匹配模式串声明切点, within()函数定义的连接点事针对目标类而言的, 而非针对运行期对象而言, within()所指定的连接点最小范围只能是类.
within(com.example.NativeWaiter): 匹配目标类 NativeWaiter 所有的方法
within(com.example.*): 匹配 com.example 包中所有的类
within(com.example.**): 匹配 com.example 包以及子孙包中的类
5.5.@within()和 @target()
@within()和 @target()只接受注解类名作为参数入参,@target(M)匹配任意标注了 @M 目标类, 而 @within(M)匹配标注了 @M 的类及子孙类, 要注意, 如果标注 @M 的是一个接口, 那么 @within 和 @target 都不会起作用, 因为 @within,@target 以及 @annotation 函数都是针对目标类而言的, 而非针对运行时引用类型而言的.
5.6.@target()和 this()
target()切点函数通过判断目标类是否按类型匹配指定类来决定连接点是否匹配, 而 this()函数则通过判断代理类是否按类型匹配指定类来决定是否和切点匹配
target():target(M)表示如果目标类按类型匹配于 M, 则目标类的所有方法都匹配切点
this(): 如果声明一个切点 tis(com.example.NativeWaiter), 如果不使用 CGLib 动态代理, 则生成的对象属于 Waiter 类型, 而非 NaiveWaiter(可以通过 instanceof 操作符来判断), 但是 NaiveWaiter 中所有方法都被织入了增强
- @Aspect
- public class TestAspect {
- // 后期增强, 织入任何运行期对象为 Seller 类型的 Bean 中
- @AfterReturning("this(com.example.SmartSeller)")
- public void thisTest(){
- System.out.println("thisTest() executed()");
- }
- }
6.@AspectJ 进阶
@AspectJ 可以使用切点函数定义切点, 还可以使用逻辑运算符对切点进行复合运算得到复合切点.
6.1. 切点复合运算
- @Aspect
- public class TestAspectJ {
- @After("within(com.example.*)&&execution(* greetTo(..))")
- public void greetTo(){
- System.out.println("--greetToFun() executed!--");
- }
- @Before("!target(com.example.NativeWaiter)&&execution(* serverTo(..))")
- public void notServeInNaiveWaiter(){
- System.out.println("--notServeInNaiveWaiter--");
- }
- @AfterReturning("target(com.example.Waiter)||target(com.example.SmartSeller)")
- public void waiterOrSeller(){
- System.out.println("--waiterOrSell() executed!--");
- }
- }
6.2. 命名切点
切点可以直接声明在增强方法处, 这种切点声明方式成为匿名切点, 匿名切点只能在声明处使用. 如果希望在其他地方重用一个切点, 则可以通过 @Pointcut 注解及切面类方法对切点进行命名.
- public class TestNamePointcut {
- @Pointcut("within(com.example.*)")
- public void inPackage(){}// 通过注解方法 inPackage()对该切点进行命名, 方法可视域修饰符为 private, 表明该命名切点只能在本切面类中使用
- @Pointcut("execution(* greetTo(..))")
- protected void greetTo(){}// 通过注解该方法 greetTo()对该切点进行命名, 方法可视域修饰符 protected, 表明该命名切点可以在当前包中的切面类, 子切面类中使用
- @Pointcut("inPackage() greetTo()")
- public void inPkgGreetTo(){}// 引用命名切点定义的切点, 本切点也是命名切点, 它对应的可视域为 public
- }
6.3. 增强织入的顺序
一个连接点可以同时匹配多个切点, 切点对应的增强在连接点上的织入顺序是如何安排呢?
如果增强在同一个切面类中声明, 则依照增强在切面类中定义的顺序进行织入
如果增强位于不同的切面类中, 且这些切面类都实现了 org.springframework.core.Ordered 接口, 则由接口方法的顺序号决定(顺序号小的先织入)
如果增强位于不同的切面类中, 且这些切面类没有实现 org.springframework.core.Ordered 接口, 则织入的顺序是不确定的
6.4. 访问连接点信息
AspectJ 使用 org.aspectj.lang.JointPoint 接口表示目标类连接点对象. 如果是环绕增强, 则使用 org.aspectj.lang.ProceedingJointPoint 表示连接点对象, 该类是 JointPoint 的子接口. 任何增强方法都可以通过将第一个入参声明为 JointPoint 访问连接点上下文信息
JointPoint
java.lang.Object[].getArgs(): 获取连接点方法运行时的入参列表
Signature getSignature(): 获取连接点的方法签名对象
java.lang.Object getTarget(): 获取连接点所在的目标对象
java.lang.Object getThis(): 获取代理对象本身
ProceedingJoinPoint
java.lang.Object proceed() throw java.lang.Throwable: 通过反射执行目标
对象连接点处的方法
java.lang.Object proceed(java.lang.Object[] args) throws java.lang.
Throwable: 通过反射执行目标对象连接点处的方法, 不过使用新的参数替换原来的入参
- @Aspect
- public class TestAspectJ {
- @After("within(com.example.*)&&execution(* greetTo(..))")
- public void greetTo(){
- System.out.println("--greetToFun() executed!--");
- }
- @Before("!target(com.example.NativeWaiter)&&execution(* serverTo(..))")
- public void notServeInNaiveWaiter(){
- System.out.println("--notServeInNaiveWaiter--");
- }
- @AfterReturning("target(com.example.Waiter)||target(com.example.SmartSeller)")
- public void waiterOrSeller(){
- System.out.println("--waiterOrSell() executed!--");
- }
- @Before("TestNamePointcut.inPkgGreetTo()")
- public void pkgGreetTo(){// 引用 TestNamePointcut.inPkgGreetTo()切点
- System.out.println("--pkgGreetTo() executed!--");
- }
- @Before("!target(com.example.NativeWaiter)&&TestNamePointcut.inPkgGreetTo()")
- public void pkgGreetToNoNaiveWaiter(){// 在复合运算中使用了命名切点
- System.out.println("--pkgGreetToNotNaiveWaiter() executed!--");
- }
- @Around("execution(* greetTo(..))&&target(com.example.NativeWaiter)")// 环绕增强
- public void joinPointAccess(ProceedingJoinPoint joinPoint) throws Throwable {// 声明连接点入参
- System.out.println("-------joinPointAccess-------");
- System.out.println("args[0]"+joinPoint.getArgs()[0]);
- System.out.println("signature"+joinPoint.getTarget().getClass());
- joinPoint.proceed();
- System.out.println("--------jointPointAccess-----------");
- }
- }
6.5. 绑定连接点方法入参
在介绍切点函数时说过 args(),this(),target(),@args(),@within(),@target()和 @annotaion()这 7 个函数除了可以指定类名外, 还可以指定参数名, 将目标对象连接点上的方法入参绑定到增强的方法中.
args: 用于绑定连接点方法的入参
@annotation: 用于绑定连接点方法的注解对象
@args: 用于绑定连接点方法入参的注解
- /**
- * 绑定连接点参数, 首先 args(name,num,..)根据增强方法入参找到
- * name 和 num 对应的类型以得到真实的切点表达式, 其次, 在该增强方
- * 法织入目标连接点时, 增强方法可以通过 num 和 name 访问到连接点的方法入参
- * @param num
- * @param name
- */
- @Before("target(com.example.NativeWaiter)&&args(name,num,..)")
- public void bindJoinPointParams(int num,String name){// 增强方法接受连接点的参数
- System.out.println("----bindJoinPointParams()----");
- System.out.println("name:"+name);
- System.out.println("num"+num);
- System.out.println("------bindJoinPointParams()---");
- }
6.6. 绑定代理对象
使用 this()或 target()函数可绑定被代理对象实例, 在通过类实例名绑定对象时, 依然具有原来连接点匹配的功能, 只不过类名是通过增强方法中同名入参的类型间接决定罢了.
- /**
- * 通过 2 处查找出 waiter 对应的 Waiter, 因而切点表达式为 this(Waiter)
- * 当增强方法织入目标连接点时, 增强方法通过 waiter 入参绑定目标对象
- * @param waiter
- */
- @Before("this(waiter)")
- public void bindProxyObj(Waiter waiter){//2
- System.out.println("----bindProxyObj()----");
- System.out.println(waiter.getClass().getName());
- System.out.println("-----bindProxyObj()------");
- }
6.7. 绑定类注解对象
@within()和 @target()函数可以将目标类的注解对象绑定到增强方法中
- /**
- * 通过 2 处查找出 n 对应 NeedTest 注解, 因而真实的切点表达式为 @within(NeedTest).
- * 当增强方法织入目标连接点时, 增强方法通过 n 入参可以引用到连接点
- * @param n
- */
- @Before("@within(n)")
- public void bindTypeAnnoObject(NeedTest n){//2
- System.out.println("----bindTypeAnnoObject()----");
- System.out.println(n.getClass().getName());
- System.out.println("-----bindTypeAnnoObject()------");
- }
6.8. 绑定返回值
在后置增强中, 可以通过 returning 绑定连接点方法的返回值
- @AfterReturning(value = "target(com.example.Waiter))",returning = "retVal")
- public void bindReturnValue(int retVal){
- System.out.println("--bindReturnValue--");
- System.out.println("returnValue"+retVal);
- System.out.println("--bindReturnValue--");
- }
6.9. 绑定抛出的异常
和通过切点函数绑定连接点信息不同, 连接点抛出的异常必须使用 AfterThrowing 注解的 throwing 成员进行绑定
- @AfterThrowing(value = "target(com.example.Waiter))",throwing = "iae")
- public void bindException(IllegalArgumentException iae){
- System.out.println("--bindException--");
- System.out.println("exception:"+iae.getMessage());
- System.out.println("--bindException--");
- }
7. 基于 Schema 配置切面
7.1. 一个简单切面的配置
- <aop:config proxy-target-class="true">
- <aop:aspect ref="adviceMethods">
- <aop:before pointcut="target(com.example.Waiter))" method="preGreeting"/>
- </aop:aspect>
- </aop:config>
- <bean id="naiveWaiter" class="com.example.NativeWaiter"></bean>
- <bean id="adviceMethods" class="com.example.schemaAspectJ.AdviceMethods"></bean>
- public class AdviceMethods {
- public void preGreeting(){// 该方法通过配置被用作增强的方法
- System.out.println("--how are you!--");
- }
- }
7.2. 配置命名切点
- <aop:aspect ref="adviceMethods">
- <aop:before pointcut="target(com.example.Waiter)) and execution(* *(..))" method="preGreeting"/>
- </aop:aspect>
- <!-- 上面定义的是匿名切点 -->
- <aop:aspect ref="adviceMethods">
- <aop:pointcut id="greetpointcut" expression="target(com.example.Waiter)) and execution(* *(..))"/>
- <aop:before pointcut-ref="greetpointcut" method="preGreeting"/>
- </aop:aspect>
- <!-- 命名式切点的配置 -->
8. 混合切面类型
基于 @AspectJ 注解方式
基于 < aop:aspect > 的方式
基于 < aop:advisor > 的方式
基于 Advisor 类的方式
AspectJ 实例的 github 地址 https://github.com/FreshSmall/SpringDemo
来源: http://www.jianshu.com/p/2853c5795f10