1.Spring对AOP的支持
- 新增了基于Schema的配置支持,为AOP专门提供了aop命名空间
- 新增了对AspectJ切点表达式语言的支持,
- 可以无缝地集成AspectJ
2.Java5.0注解知识快速进阶
注解是代码的附属信息,它遵循一个基本原则:注解不能干扰程序代码的运行,无论增加或删除注解,代码都能够正常运行。Java语言解释器会忽略这些注解,而由第三方工具负责对注解进行处理。第三方工具可以利用代码中的注解间接控制程序代码的运行,它们通过Java反射机制读取注解的信息,并根据这些信息更改目标程序的逻辑,而这正是Spring AOP对@AspectJ提供支持所采取的办法。
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(public (..)):匹配所有的目标类的public方法,第一个代表返回
- 通过类定义切点
- 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:通过反射执行目标对象连接点处的方法,不过使用新的参数替换原来的入参
- java.lang.Object proceed() throw 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类的方式