Spring基础:AOP编程(4)

[size=x-large][b]基于AspectJ的AOP编程[/b][/size]

AspectJ的切点函数非常精妙,基本上可以覆盖我们编程中可以遇到的所有连接点,因为Spring仅支持方法级别的切点,所以Spring中的切点函数是AspectJ中的一个子集。掌握切点函数即掌握了AspectJ语法的基础。

首先是在切点函数中需要使用的通配符的概念:
*:匹配任意字符
..:匹配任意字符,表示类的时候必须和*联合使用,表示入参时可以单独使用
+:表示匹配目标类以及目标类的子类

execution(),within()可以使用全部通配符。
args(),target(),this()仅能使用+通配符,不过使用和不使用的效果是一样的。
其他的切点函数不能使用通配符。

切点函数的分类:
[list]
[*]方法切点函数
[*]方法入参切点函数
[*]目标类切点函数
[*]代理类起点函数
[/list]

在AspectJ中我们使用注解来定义增强类型(即增强逻辑的织入位置)
[list]
[*]@Before:前置增强
[*]@AfterReturning:后置增强
[*]@Around:环绕增强
[*]AfterThrowing:抛出异常增强
[*]After:不管是否抛出异常,都会执行,相当于Final增强
[*]DeclareParents:引介增强
[/list]

在使用AspectJ配置文件的时候,需要在位置文件中加入

<aop:aspectj-autoproxy proxy-target-class="true"/>

proxy-target-class默认是false,表示采用JDK动态代理生成目标类,如果设为true,则使用CGLib代理。
********这两者有很大区别:JDK动态代理仅能生成目标类的[color=red]接口[/color]类型,而CGLib则生成目标[color=red]类[/color]的代理类型。这对于切点函数的意义重大,这点一定需要牢记!!

切点函数详解:
1.@annotation()
表示当方法标注类设定的注解,则该方法匹配次切点

//标注了NeedTes注解的方法,在返回时将被注入以下增强
@Aspect
public class TestAspect {
@AfterReturning("@annotation(com.firethewhole.maventest08.aspectj.anno.NeedTest)")
public void needTestFun() {
System.out.println("needTestFun() executed");
}
}


// 目标类
public class NaughtyWaiter implements Waiter {
@NeedTest
public void greetTo(String clientName) {
System.out.println("NaughtyWaiter: greet to " + clientName);
}
}


<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="naiveWaiter" class="com.firethewhole.maventest08.NaiveWaiter"/>
<bean id="naughtWaiter" class="com.firethewhole.maventest08.NaughtyWaiter"/>
<bean class="com.firethewhole.maventest08.aspectj.fun.TestAspect"/>


ApplicationContext ctx = new ClassPathXmlApplicationContext("com/firethewhole/maventest08/aspectj/example/beans.xml");
NaiveWaiter naiveWaiter = (NaiveWaiter) ctx.getBean("naiveWaiter");
NaughtyWaiter naughtWaiter = (NaughtyWaiter) ctx.getBean("naughtWaiter");
naiveWaiter.greetTo("John");
naughtWaiter.greetTo("Tom");

输出:
[color=blue]
NaiveWaiter.greet to John
NaughtyWaiter: greet to Tom
needTestFun() executed
[/color]

2.execution()
表示执行方法匹配切点函数,这个切点函数是最常用的。
2.1 通过方法签名定义切点
execution(public * *(..)):匹配public方法。
execution(* *To(..)):匹配所有以To结尾的方法。

2.2 通过类定义切点
execution(com.firethewhole.maventest08.Waiter.*(..)):匹配Waiter接口的所有方法,所以NaughtyWaiter和NaiveWaiter中greetTo和serveTo方法都会被织入增强。
execution(com.firethewhole.maventest08.Waiter+.*(..)):不光匹配Waiter接口中定义的增强,还匹配NaiveWaiter的smile方法和NaughtyWaiter中的joke方法。

2.3 通过类包定义切点
execution(* com.firethewhole.maventest08.*(..)):匹配com.firethewhole.maventest08包下面所有类的所有方法。
execution(* com.firethewhole.maventest08..*(..)):不光包括上面的方法,还包括类这个包里面的子孙包中的类的所有方法。
execution(* com.firethewhole.maventest08..*.*Dao.find*(..)):匹配com.firethewhole.maventest08这个包,包括子孙包中以Dao结尾的类中,以find开头的方法。

2.4 通过方法入参定义切点
execution(* joke(String,int)):匹配joke方法,且第一个参数类型必须是String,第二个参数类型必须是int,有且只有这两个参数,比如NaughtyWaiter中的joke方法。
execution(* joke(String,*)):第二个参数可以是任意类型,但是必须是两个参数。
execution(* joke(String,..)):第一个参数必须是String类型,可以只有一个参数,也可以有多个参数。
execution(* joke(Object+)):匹配Object类型或者Object的子类,即所有的引用类型变量,是否包含基本类型,需要测试才知道。execution(* joke(Object))则仅匹配Object,其他入参类型不匹配。

3.args()
表示入参类型匹配,注意这里是实际入参类型动态匹配,所以也包括类子类。
execution(com.firethewhole.maventest08.Waiter):如果入参是Waiter或者NavieWaiter或者NaughtyWaiter,则匹配该切点。

4.@args()
如果入参标注类该切点,则匹配该切点。
这里涉及到类3个概念,切点函数所标识的注解类型,入参类型,运行时实际入参类型类型。
需要记住:标注注解的类型如果在继承关系上是入参类型的子类,则该类型及其子类都匹配切点,如果标注注解的类型在继承关系上是入参类型法父类,则都不匹配。

5.within()
属于execution()切点函数的子集,连接点仅能指定到类级别。

6.@within()
匹配标注类指定注解的类,包括该指定注解类的子类。

7.@target()
仅匹配标注类指定注解的类,不包括该标注注解类的子类!

8.target()
匹配切点函数所指定的类型及其子类的所有方法

9.this()
如果该代理对象按类型匹配目标类,则匹配该切点。这个函数和target是有区别的,在使用引介增强是,为NaiveWaiter加入SmartSeller的增强后,this(com.firethewhole.maventest08.Seller)将匹配NaiveWaiter类。

// 引介增强,会NaiveWaiter织入Seller增强,实现类为SmartSeller
@Aspect
public class EnableSellerAspect {
@DeclareParents(value="com.firethewhole.maventest08.NaiveWaiter", defaultImpl=SmartSeller.class)
public Seller seller;
}


// 代理增强切点
@Aspect
public class TestAspect {
@AfterReturning("this(com.firethewhole.maventest08.Seller)")
public void thisTest() {
System.out.println("this Test() executed");
}
}


ApplicationContext ctx = new ClassPathXmlApplicationContext("com/firethewhole/maventest08/aspectj/example/beans.xml");
Waiter naiveWaiter = (Waiter) ctx.getBean("naiveWaiter");
naiveWaiter.greetTo("John");
naiveWaiter.serveTo("John");
((Seller)naiveWaiter).sell("Beer", "John");

输出:
[color=blue]
NaiveWaiter.greet to John
this Test() executed
NaiveWaiter.serve to John
this Test() executed
SmartSeller: sell Beer to John
[/color]
*****这里和书上不一样,书上最后一个sell方法也被织入增强,但是实际上是没有。

切点的复合运算
使用&& || !或者and or not组合各个切点函数

@Aspect
public class TestAspect {
@After("within(com.firethewhole.maventest08.*) && execution(* greetTo(..))")
public void greetToFun() {
System.out.println("--greetToFun() executed!--");
}

@Before("!target(com.firethewhole.maventest08.NaiveWaiter) && execution(* serveTo(..))")
public void notServeInNaiveWaiter() {
System.out.println("--notServeInWaiveWaiter() executed!--");
}

@AfterReturning("target(com.firethewhole.maventest08.Waiter) || target(com.firethewhole.maventest08.Seller)")
public void waiterOrSeller() {
System.out.println("--waiterOrSeller() executed!--");
}
}


命名切点
直接在AspectJ类中使用的切点函数是匿名切点,我们可以在一个专门的类中,将所有切点函数都命名然后使用。

// 注意这里的访问修饰符,private表示仅能在该类中使用,和正常的修饰符是一样的
public class TestNamePointcut {
@Pointcut("within(com.firethewhole.maventest08.*)")
private void inPackage() {
}

@Pointcut("execution(* greetTo(..))")
protected void greetTo() {
}

@Pointcut("inPackage() and greetTo()")
public void inPkgGreetTo() {
}
}


@Aspect
public class TestAspect {
@Before("com.firethewhole.maventest08.aspectj.advanced.TestNamePointcut.inPkgGreetTo()")
public void pkgGreetTo() {
System.out.println("--pkgGreetTo() executed!--");
}

@Before("!target(com.firethewhole.maventest08.NaiveWaiter) && com.firethewhole.maventest08.aspectj.advanced.TestNamePointcut.inPkgGreetTo()")
public void pkgGreetToNoNaiveWaiter() {
System.out.println("--pkgGreetToNoNaiveWaiter() executed!--");
}
}


增强织入顺序
不同的切面类靠实现Order接口来决定,相同的切面类中的增强是按照定义的顺序织入的。

访问连接点信息

// @Around增强需要使用ProceedingJoinPoint,其他的增强类型使用JoinPoint
@Aspect
public class TestAspect {
@Around("execution(* greetTo(..)) && target(com.firethewhole.maventest08.NaiveWaiter)")
public void joinPointAccess(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("-----joinPointAccess-----");
System.out.println("arg[0]:" + pjp.getArgs()[0]);
System.out.println("signature:" + pjp.getTarget().getClass());
pjp.proceed();
System.out.println("-----JoinPointAccess-----");
}
}

输出:
[color=blue]
-----joinPointAccess-----
arg[0]:John
signature:class com.firethewhole.maventest08.NaiveWaiter
NaiveWaiter.greet to John
-----JoinPointAccess-----
[/color]

绑定连接点方法入参
这个例子一定要用CGLib代理

// args()会根据参数中的name,num自动去增强逻辑bindJoinPointParams中查找实际的类型信息
@Aspect
public class TestAspect {
@Before("target(com.firethewhole.maventest08.NaiveWaiter) && 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-----");
}
}


ApplicationContext ctx = new ClassPathXmlApplicationContext("com/firethewhole/maventest08/aspectj/example/beans.xml");
NaiveWaiter naiveWaiter = (NaiveWaiter) ctx.getBean("naiveWaiter");
naiveWaiter.smile("John", 2);

输出:
[color=blue]
-----bindJoinPointParams-----
name:John
num:2
-----bindJoinPointParams-----
NaiveWaiter.smile to John 2 times
[/color]

绑定代理对象

@Aspect
public class TestAspect {
@Before("this(waiter)")
public void bindProxyObj(Waiter waiter) {
System.out.println("-----bindProxyObj-----");
System.out.println(waiter.getClass().getName());
System.out.println("-----bindProxyObj-----");
}
}

输出:
[color=blue]
-----bindProxyObj-----
com.firethewhole.maventest08.NaiveWaiter$$EnhancerBySpringCGLIB$$6f6d5c3b
-----bindProxyObj-----
NaiveWaiter.greet to John
[/color]
可以看到实际生成的代理对象的类型。

绑定类注解对象
在使用@target或者@args时指定类注解类型,可以在增强逻辑中使用。

绑定返回值

// 在各种增强类型中,如果有返回值相关的信息可以指定
@Aspect
public class TestAspect {
@AfterReturning(value="target(com.firethewhole.maventest08.SmartSeller)",returning="retVal")
public void bindReturnValue(int retVal) {
System.out.println("-----bindReturnValue-----");
System.out.println("returnValue:" + retVal);
System.out.println("-----bindReturnValue-----");
}
}

输出:
[color=blue]
SmartSeller: sell Beer to John
-----bindReturnValue-----
returnValue:100
-----bindReturnValue-----
[/color]

绑定抛出异常

@Aspect
public class TestAspect {
@AfterThrowing(value="target(com.firethewhole.maventest08.SmartSeller)", throwing="iae")
public void bindException(IllegalArgumentException iae) {
System.out.println("-----bindException-----");
System.out.println("exception:" + iae.getMessage());
System.out.println("-----bindException-----");
}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值