Spring基础:AOP编程(4)

基于AspectJ的AOP编程

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

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

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

切点函数的分类:
  • 方法切点函数
  • 方法入参切点函数
  • 目标类切点函数
  • 代理类起点函数


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


在使用AspectJ配置文件的时候,需要在位置文件中加入
<aop:aspectj-autoproxy proxy-target-class="true"/>

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

切点函数详解:
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");

输出:

NaiveWaiter.greet to John
NaughtyWaiter: greet to Tom
needTestFun() executed


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");

输出:

NaiveWaiter.greet to John
this Test() executed
NaiveWaiter.serve to John
this Test() executed
SmartSeller: sell Beer to John

*****这里和书上不一样,书上最后一个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-----");
    }
}

输出:

-----joinPointAccess-----
arg[0]:John
signature:class com.firethewhole.maventest08.NaiveWaiter
NaiveWaiter.greet to John
-----JoinPointAccess-----


绑定连接点方法入参
这个例子一定要用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);

输出:

-----bindJoinPointParams-----
name:John
num:2
-----bindJoinPointParams-----
NaiveWaiter.smile to John 2 times


绑定代理对象
@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-----");
    }
}

输出:

-----bindProxyObj-----
com.firethewhole.maventest08.NaiveWaiter$$EnhancerBySpringCGLIB$$6f6d5c3b
-----bindProxyObj-----
NaiveWaiter.greet to John

可以看到实际生成的代理对象的类型。

绑定类注解对象
在使用@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-----");
    }
}

输出:

SmartSeller: sell Beer to John
-----bindReturnValue-----
returnValue:100
-----bindReturnValue-----


绑定抛出异常
@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-----");
    }
}

猜你喜欢

转载自foreversky12.iteye.com/blog/2341332