Spring4学习:基于@AspectJ和Schema的AOP

一、使用@AspectJ

Spring在处理@AspectJ注解表达式时,需要将Spring的asm模块添加到类路径中。Spring采用AspectJ提供的@AspectJ注解类库及相应的解析类库,需要在pom.xml文件中添加aspectj.weaver和aspectj.tools类包的依赖。

1、@AspectJ使用实例:

首先是业务类的代码:

public class NaiveWaiter implements Waiter {
	public void greetTo(String clientName) {
		System.out.println("NaiveWaiter:greet to "+clientName+"...");
	}	
}
然后使用@AspectJ注解定义一个切面:

@AspectJ
public class PreGreetingAspect{
	@Before("execution(* greetTo(..))")
	public void beforeGreeting(){
		System.out.println("How are you");
	}
}
最后通过AspectJProxyFactory为NativeWaiter生成织入PreGreetingAspect切面的代理:

public class AspectJProxyTest {
	@Test
	public void proxy(){
		Waiter target = new NaiveWaiter();
		AspectJProxyFactory factory = new AspectJProxyFactory();
		factory.setTarget(target);
		factory.addAspect(PreGreetingAspect.class);
		Waiter proxy = factory.getProxy();
		proxy.greetTo("John");
		proxy.serveTo("John");
	}
}

2、通过配置使用@AspectJ切面

Spring4学习:基于@AspectJ和Schema的AOP_第1张图片
AnnotationAwareAspectJAutoProxyCreator能够将@AspectJ注解切面类自动织入目标Bean中,PreGreetingAspect是使用@AspectJ注解描述的切面类,而NativeWaiter是匹配切点的目标类。

以下使用基于Schema的aop命名空间进行配置:


     
     
     
首先在配置文件中引入aop命名空间,然后通过aop命名空间的自动为Spring容器中那些匹配@AspectJ切面的Bean创建代理,完成切面织入。有一个属性,默认为false,表示使用JDK动态代理技术织入增强;配置为true时,使用CGLib动态代理技术。

二、@AspectJ语法基础

1、切点表达式函数

Spring支持9个@AspectJ切点表达式函数,大致分为4种类型:

(1)方法切点函数:通过描述目标类方法 的信息定义连接点

(2)方法入参切点函数:通过描述目标类方法入参的信息定义连接点

(3)目标类切点函数:通过描述目标类类型的信息定义连接点

(4)代理类切点函数:通过描述目标类的代理类的信息定义连接点

这4种类型的切点函数如下:

Spring4学习:基于@AspectJ和Schema的AOP_第2张图片

2、在函数入参中使用通配符

@AspectJ支持3种通配符

(1)*:匹配上下文中任意一个字符

(2)..:匹配上下文中任意多个字符,在表示类时,必须和*联合使用

(3)+:按类型匹配指定类的所有类(继承或扩展指定类的所有类还有类本身),必须跟在类名后面。

@AspectJ函数按其是否支持通配符及支持程度,分为3类:

(1)支持所有的通配符:execution()和within(),如within(com.smart.*)、within(com.smart.service..*.*Service+)

(2)仅支持“+”通配符:args()、this()和target()

(3)不支持通配符:@args()、@within()、@target()和@annotation()

3、逻辑运算符

Spring支持一下切点运算符:

&&:与操作符,相当于切点的交集运算。由于&是XML特殊字符,Spring提供了and,如within(com.smart..*)and args(String)表示在com,smart包下所有类(当前包及子孙包)拥有一个String入参的方法。

||:或操作符,相当于切点的并集运算,or是等效的操作符。

!:非操作符,相当于切点的反集运算,not是等效的操作符。

4、不同增强类型的注解类

注解类的成员中value和argNames的含义是相同的:

value:用于定义切点

argNames:指定注解所标注增强方法的参数名,多个参数名用逗号分隔。

(1)@Before:有两个成员value和argNames

(2)@AfterReturning:有4个成员value和argNames,还有

pointcut:表示切点的信息,显示指定pointcut值,会覆盖value的设置值

returning:将目标对象方法的返回值绑定给增强的方法

(3)@Around:有两个成员:value和argNames

(4)@AfterThrowing:有4个成员value和argNames,还有

pointcut:表示切点的信息,显示指定pointcut值,会覆盖value的设置值

throwing:将抛出的异常绑定到增强方法中

(5)@After:Final增强,不管是抛出异常还是正常退出,该增强都会得到执行。有两个成员value和argNames

(6)@DeclareParents:引介增强,相当于IntroductionInterceptor,有两个成员value和defaultImpl,defaultImpl是默认的接口实现类

5、引介增强的用法

下面通过切面技术将SmartSeller加入到NativeWaiter中,让NativeWaiter实现Seller的接口:

@Aspect
public class EnableSellerAspect {
   @DeclareParents(value="com.smart.NaiveWaiter",defaultImpl=SmartSeller.class)
   public  Seller seller;
}
在Spring配置文件中配置好切面和NativeWaiter Bean


	
	
测试代码:

public class DeclaredParentsTest{
	public static void main(String[] args){
		String configPath = "com/smart/aspectj/basic/beans.xml";
		ApplicationContext ctx = new ClassPathApplicationContext(configPath);
		Waiter waiter = (Waiter)ctx.getBean("waiter");
		waiter.greetTo("John");
		Seller seller = (Seller)waiter;
		seller.sell("Beer","John");
	}
}

三、切点函数详解

1、@annotation()

@annotation()表示标注了某个注解的所有方法。比如:@AfterReturning("@annotation(com.smart.anno.NeedTest)")表示对标注了@NeedTest的所有方法进行后置增强。

2、execution()

execution()是最常用的切点函数,其语法如下:
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
除了返回类型模式、方法名模式和参数模式外,其他项都是可选的。
(1)通过方法签名定义切点
比如:execution(public * *(..)):匹配所有目标类的public方法
  execution(* *To(..)):匹配目标类所有以To为后缀的方法
(2)通过类定义切点
比如:execution(* com.smart.Waiter.*(..)):匹配Waiter接口的所有方法
  execution(* com.smart.Waiter+.*(..)):匹配Waiter接口及所有实现类的方法
(3)通过类包定义切点
比如:execution(* com.smart.*(..)):匹配com.smart包下所有类的所有方法
  execution(* com.smart..*(..)):匹配com.smart包、子孙包下所有类的所有方法,当“..”出现在类名时,后面必须跟“*”,表示包、子孙包下的所有类。
(4)通过方法入参定义切点
可以使用“*”和“..”通配符。其中,“*”表示任意类型的参数;而“..”表示任意类型的参数且参数个数不限
比如:execution(* joke(String,int)):匹配joke(String,int)方法。如果方法中的入参类型是java.lang包下的类,则可以直接使用类名;否则必须使用全限定类名,如joke(java.util.List,int)
  execution(* joke(String,..)):匹配目标类中的joke()方法,该方法的第一个入参为String,后面可以有任意个入参且入参类型不限。
  execution(* joke(Object+)):匹配目标类中的joke()方法,方法拥有一个入参,且参数是Object类型或该类的子类。

3、args()和@args()

args()函数的入参是类名,表示目标类方法入参对象是指定类(包含子类)时,切点匹配。args(com.smart.Waiter)等价于execution(* *(com.smart.Waiter+))
@args()函数的入参必须是注解类的类名,当方法的运行时入参对象标注了指定的注解时,匹配切点。

4、within()

within()函数定义的连接点是针对目标类而言的,而非针对运行期对象的类型而言,这一点和execution()是相同的。但within()所指定的连接点最小范围只能是类,而execution()所指定的连接点可以大到包,小到方法入参。within()语法如下:
within(<类匹配模式>)

5、@within()和@target()

@within(M)匹配标注了@M的类及子孙类,@tartget(M)匹配任意标注了@M的目标类
注意:如果标注@M注解的是一个接口,则所有实现该接口的类并不匹配@within(M)。这是因为@within()、@target()及@annotation()函数都是针对目标类而言的,而非针对运行时的引用类型而言的。

6、target()和this()

tartget()切点函数通过判断目标类是否按类型匹配指定类来决定连接点是否匹配,this()函数则通过判断代理类是否按类型匹配指定类来决定是否和切点匹配。二者都只接受类名的入参。

四、@AspectJ进阶

1、切点符合运算

@Aspect
public class TestAspect {
	@Before("!target(com.smart.NaiveWaiter) && execution(* serveTo(..)))")
	public void notServeInNaiveWaiter() {
		System.out.println("--notServeInNaiveWaiter() executed!--");
	}
	@After("within(com.smart.*) && execution(* greetTo(..)))")
	public void greeToFun() {
		System.out.println("--greeToFun() executed!--");
	}
	
	@AfterReturning("target(com.smart.Waiter) || target(com.smart.Seller)")
	public void waiterOrSeller(){
		System.out.println("--waiterOrSeller() executed!--");
	}
}

2、命名切点

Spring4学习:基于@AspectJ和Schema的AOP_第3张图片

命名切点的使用类方法作为切点的名称,此外方法的访问修饰符还控制了切点的可引用性。如下是命名切点的结构:

Spring4学习:基于@AspectJ和Schema的AOP_第4张图片

命名切点定义好后,可以在定义切面时通过名称引用切点,如下:

Spring4学习:基于@AspectJ和Schema的AOP_第5张图片

3、增强织入的顺序

(1)如果增强在同一个切面类中声明,则依照增强在切面类中定义的顺序进行织入

(2)如果增强位于不同的切面类中,且这些切面类都实现了org.springframework.core.Ordered接口,则由接口方法的顺序号决定(顺序号小的先织入)

(3)如果增强位于不同的切面类中,且这些切面类没有实现org.springframework.core.Ordered接口,则织入的顺序是不确定的。

五、基于Schema配置切面

1、基于Schema配置切面的实例

Spring4学习:基于@AspectJ和Schema的AOP_第6张图片

使用元素标签定义切面,其内部可以定义多个增强。在元素中可以定义多个切面。

2、配置命名切点

Spring4学习:基于@AspectJ和Schema的AOP_第7张图片

如果位于元素中,则命名切点只能被当前的元素访问到,定义在元素中,可以被整个元素中定义的所有增强访问。如果在元素下直接定义则必须保证之前定义。而在中没有先后顺序的要求。

3、各种增强类型的配置


	      
		
			
			
			
			
			
            
		
	
    
	
	
	
	

4、Advisor配置

通过配置一个Advisor,通过advice-ref属性引用基于接口定义的增强,通过pointcut定义切点表达式,或者通过pointcut-ref引用一个命名的切点。如下:

Spring4学习:基于@AspectJ和Schema的AOP_第8张图片

六、各种切面类型总结

(1)基于@AspectJ注解的方式:适用项目采用Java5.0

(2)基于的方式:适用项目只能使用低版本的JDK

(3)基于的方式:适用正在升级一个基于低版本Spring AOP开发的项目,可以复用已经存在的Advice类

(4)基于Advisor类的方式:适用项目只能使用低版本的Spring

切面不同定义方式具体实现比较:

Spring4学习:基于@AspectJ和Schema的AOP_第9张图片



你可能感兴趣的:(Spring)