Spring笔记——AOP(注解方式)

在Java EE应用中,常常通过AOP来处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等。

1.AOP的基本概念

AOP从程序运行角度考虑程序的流程,提取业务处理过程的切面。AOP面向的是程序运行中各个步骤,希望以更好的方式来组合业务处理的步骤。
AOP框架并不与特定的代码耦合,AOP框架能处理程序中特定切入点(Pointcut),而不与具体某个具体类耦合。

AOP框架具有如下两个特征:
☞ 各步骤之间良好隔离性
☞ 源代码无关性

下面是关于面向切面编程的一些术语:
☞ 切面(AspectJ):业务流程运行的某个特定步骤,也就是应用运行过程的关注点,关注点可能横切多个对象,所以常常也称为横切关注点
☞ 连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出。Spring AOP中,连接点总是方法的调用
☞ 增强处理(Advice):AOP框架在特定的切入点执行的增强处理。处理有“around”、“before”和“after”等类型
☞ 切入点(Pointcut):可以插入增强处理的连接点。简而言之,当某个连接点满足指定要求时,该连接点将被添加增强处理,该连接点也就变成了切入点。Spring默认使用AspectJ切入点语法
☞ 引入:将方法或字段添加到被处理的类中。Spring允许引入新的接口到任何被处理的对象。例如,你可以使用一个引入,使任何对象实现IsModified接口,以此来简化缓存
☞ 目标对象:被AOP框架进行增强处理的对象,也被称为被增强的对象
☞ AOP代理:AOP框架创建的对象,简单的说,代理就是对目标对象的加强。Spring中的AOP代理可以是JDK动态代理,也可以是CGLIB代理
☞ 织入(Weaving):将增强处理添加到目标对象中,并创建一个被增强的对象(AOP代理)的过程就是织入。织入有两种实现方式:编译时增强(例如AspectJ)和运行时增强(例如CGLIB)。Spring和其他纯Java AOP框架一样,在运行时完成织入。

由前面的介绍知道:AOP代理其实是由AOP框架动态生成的一个对象,该对象可作为目标对象使用。AOP代理包含了目标对象的全部方法,但AOP代理中的方法与目标对象的方法存在差异:AOP方法在特定切入点添加了增强处理,并回调了目标对象的方法

2.Spring的AOP支持

Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也有IOC容器负责管理。因此,AOP代理可以直接使用容器中的其他Bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了。
Spring也可以使用CGLIB代理,在需要代理类而不是代理接口的时候,Spring自动会切换为使用CGLIB代理。
Spring目前仅支持将方法调用作为连接点(Joinpoint),如果需要把对Field的访问和更新也作为增强处理的连接点,则可以考虑使用AspectJ。
纵观AOP编程,其中需要程序员参与的只有三个部分:
☞ 定义普通业务组件
☞ 定义切入点,一个切入点可能横切多个业务组件
☞ 定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作

3.基于Annotation的“零配置”方式

为了启用Spring对@AspectJ切面配置的支持,并保证Spring容器中的目标Bean被一个或多个切面自动增强,必须在Spring配置文件中配置如下片段:


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    
    <aop:aspectj-autoproxy/>
beans>

如果不打算使用Spring的XML Schema配置方式,则应该在Spring配置文件中增加如下片段来启用@AspectJ支持。


<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />

为了在Spring应用中启动@AspectJ支持,还需要在应用的类加载路径中增加两个AspectJ库:aspectjweaver.jar和aspectjrt.jar

①定义切面

当启动了@AspectJ支持后,只要我们在Spring容器中配置一个带@Aspect注释的Bean,Spring将会自动识别该Bean,并将该Bean作为切面处理。
使用@Aspect标注一个Java类,该Java类将会作为切面Bean,如下面的代码片段所示。

@Aspect
public class LogAspect {
    // 定义该类的其他内容

}

切面类(用@Aspect修饰的类)和其他类一样可以有方法、属性定义,还可能包含切入点、增强处理定义。
当我们使用@Aspect来修饰一个Java类之后,Spring将不会把该Bean当做组件Bean处理,因此负责自动增强的后处理Bean将会略过该Bean,不会对该Bean进行任何增强处理。

②定义Before曾强处理

当我们在一个切面类里使用@Before来标注一个方法时,该方法将作为Before增强处理。使用@Before标注时,通常需要指定一个value属性,该属性值指定一个切入点表达式,用于指定该增强处理将被织入哪些切入点。例如:

@Component
@Aspect
public class BeforeAdviceTest {

    // 匹配org.crazyit.app.service.impl包下所有类的所有方法的执行作为切入点
    @Before("execution(* org.crazyit.app.service.impl.*.*(..))")
    public void authority() {
        System.out.println("模拟执行权限检查");
    }
}

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    
    <context:component-scan base-package="main.java.service.impl,main.java.aspect"/>
    
    <aop:aspectj-autoproxy />
beans>

注:需要在加入aopalliance.jar

③ 定义AfterReturning增强处理

类似于使用@Before注解可标注Before增强处理,使用@AfterReturning来标注一个AfterReturning增强处理,AfterReturning增强处理将在目标方法正常完成后被织入

使用@AfterReturning注解时可指定如下两个常用属性:
☞ pointcut/value:这两个属性的作用是一样的,他们都用于指定该切入点对应的切入表达式。当指定了pointcut属性值后,value属性值将会被覆盖
☞ returning:指定一个返回值形参名,增强处理定义的方法可通过该形参名来访问目标方法的返回值

@Component
@Aspect
public class AfterReturningAdviceTest {

    @AfterReturning(returning = "rvt", pointcut = "execution(* main.java.service.*.*(..))")
    public void log(Object rvt) {
        System.out.println("获取目标方法返回值:" + rvt);
        System.out.println("模拟记录日志功能...");
    }
}

正如上面的程序中看到的,程序中使用@AfterReturning时,指定了一个returning属性,该属性值为rvt,这表明允许在增强处理方法(log方法)中使用名为rvt的形参,该形参代表目标方法的返回值。

④ 定义AfterThrowing增强处理

使用@AfterThrowing注解可用于标注一个AfterThrowing增强处理,AfterThrowing增强处理主要用于处理程序中未处理的异常。
使用@AfterThrowing时,可指定如下两个常用属性:
☞ pointcut/value:这两个属性的作用是一样的,他们都用于指定该切入点对应的切入表达式。当指定了pointcut属性值后,value属性值将会被覆盖
☞ returning:指定一个返回值形参名,增强处理定义的方法可通过该形参名来访问目标方法中所抛出的异常对象

@Component
@Aspect
public class AfterThrowingAdviceTest {

    @AfterThrowing(throwing = "ex", pointcut = "execution(* main.java.service.*.*(..))")
    public void doRecoveryActions(Throwable ex) {
        System.out.println("目标方法中抛出的异常:" + ex);
        System.out.println("模拟抛出异常后的增强处理...");
    }
}

正如上面的程序中看到的,程序中使用 @AfterThrowing时,指定了一个throwing属性,该属性这为ex,这允许在增强处理方法中使用名为ex的形参,该形参代表目标方法所抛出的异常

@Component
public class Chinese implements Person {

    @Override
    public String sayHello(String name) {

        try {
            System.out.println("sayHello方法开始被执行...");
            new java.io.FileInputStream("a.txt");
        } catch (Exception ex) {
            System.out.println("目标类的异常处理" + ex.getMessage());
        }
        return name + " Hello, Spring AOP";
    }

    public void eat(String food) {
        int a = 5 / 0;
        System.out.println("我正在吃:" + food);
    }
}

上面的程序中的sayHello()和eat()两个方法都会抛出异常,但sayHello()方法中异常将由该方法显示捕捉,所以Spring AOP不会处理该异常;而eat()方法将抛出一个AirthmeticException异常,且该异常没有被任何程序所处理,故Spring AOP会对该异常进行处理。

模拟执行权限检查...
sayHello方法开始被执行...
目标类的异常处理a.txt (系统找不到指定的文件。)
获取目标方法返回值:qiuxiao Hello, Spring AOP
模拟记录日志功能...
qiuxiao Hello, Spring AOP
模拟执行权限检查...
目标方法中抛出的异常:java.lang.ArithmeticException: / by zero
模拟抛出异常后的增强处理...
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at main.java.service.impl.Chinese.eat(Chinese.java:23)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:50)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:55)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:50)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    at com.sun.proxy.$Proxy8.eat(Unknown Source)
    at main.test.Test.main(Test.java:13)

注意:使用throwing属性还有一个额外的作用:它可用于限定切入点只匹配指定类型的异常——加入在上面的doRecoveryActions()方法中定义了ex形参的类型是NullPointerException,则该切入点只匹配抛出NullPointerException异常的情况。上面doRecoveryActions()方法的ex形参类型是Throwable,这表明该切入点可匹配抛出任何异常的情况

AOP的AfterThrowing处理虽然可以对目标方法的异常进行处理,但是这种处理与直接使用catch捕捉不同:catch捕捉意味着完全处理该异常,如果catch块中没有重新抛出新异常,则该方法可以正常结束;而AfterThrowing处理虽然处理了该异常,但它不能完全处理该异常,该异常依然会传播到上一级调用者

⑤After增强处理

Spring还提供了一个After增强处理,它与AfterReturning增强处理有点相似,但也有区别:
☞ AfterReturning增强处理只有在目标方法成功完成后才会被织入
☞ After增强处理不管目标方法如何约束(包括成功完成和遇到异常中止两种情况),它都会被织入

因为不论一个方法是如何结束的,After增强处理都会被织入,因此After增强处理必须准备处理正常返回和异常返回两种情况,这种增强处理通常用于释放资源。
使用@After时,需要指定一个value属性,该属性值用于指定该增强处理被织入的切入点

@Component
@Aspect
public class AfterAdviceTest {

    @After(value = "execution(* main.java.service.*.*(..))")
    public void release() {
        System.out.println("模拟方法结束后的释放资源...");
    }
}

⑥Around增强处理

@Around用于标注Around增强处理,Around曾强处理是功能比较强大的增强处理,它近似于Before和AfterReturning增强处理的总和,Around增强处理既可在执行目标方法前织入增强动作,也可在执行目标方法之后织入增强动作。
与Before、AfterReturning增强处理不同的是,Around增强处理甚至可以决定目标方法在什么时候执行,如何执行,甚至可以完全阻止目标方法的执行。

Around增强处理可以改变执行目标方法的参数值,也可改变执行目标方法之后的返回值。

Around增强处理的功能虽然强大,但通常需要在线程安全的环境下使用。如果需要目标方法执行之前和之后共享某种状态数据,则应该考虑使用Around增强处理;尤其是需要使用增强处理阻止目标的执行,或需要改变目标方法返回值时,则只能使用Around增强处理了。

使用@Around时需要指定一个value属性,该属性指定增强处理被织入的切入点。

当定义一个Around增强处理方法时,该方法的第一个参数必须是ProceedingJoinPoint类型(至少包含一个形参),在增强处理方法体内,调用ProceedingJoinPoint的proceed()方法才会执行目标方法——这就是Around增强处理可以完全控制目标方法执行时机、如何执行的关键;如果程序没有调用ProceedingJoinPoint的proceed()方法,则目标方法不会被执行。

调用ProceedingJoinPoint的proceed()方法时,还可以传入一个Object[]对象,该数组中的值将被传入目标方法作为执行方法的实参。

@Component
@Aspect
public class AroundAdviceTest {

    @Around(value = "execution(* main.java.service.*.*(..))")
    public Object processTx(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("执行目标方法之前,模拟开始事物...");
        // 执行目标方法,并保存目标方法执行后的回值
        Object rvt = jp.proceed(new String[] { "被改变的参数" });
        System.out.println("执行目标方法之后,模拟结束事物...");
        return rvt + "新增的内容";
    }
}
执行目标方法之前,模拟开始事物...
模拟执行权限检查...
sayHello方法开始被执行...
目标类的异常处理a.txt (系统找不到指定的文件。)
执行目标方法之后,模拟结束事物...
获取目标方法返回值:被改变的参数 Hello, Spring AOP新增的内容
模拟记录日志功能...
模拟方法结束后的释放资源...
被改变的参数 Hello, Spring AOP新增的内容
执行目标方法之前,模拟开始事物...
模拟执行权限检查...
我正在吃:被改变的参数
目标方法中抛出的异常:java.lang.ArithmeticException: / by zero
模拟抛出异常后的增强处理...
模拟方法结束后的释放资源...
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at main.java.service.impl.Chinese.eat(Chinese.java:24)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:50)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:91)
    at main.java.aspect.AroundAdviceTest.processTx(AroundAdviceTest.java:16)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610)
    at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:65)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:55)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:50)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:42)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    at com.sun.proxy.$Proxy10.eat(Unknown Source)
    at main.test.Test.main(Test.java:13)

当调用ProceedingJoinPoint的proceed()方法时,传入的Object[]参数值将作为目标方法的参数,如果传入的Object[]数组长度与目标方法所需要参数的个数不相等,或者Object[]数组元素与目标方法所需参数的类型不匹配,程序就会出现异常。

为了能获取目标方法的参数的个数和类型,需要增强处理方法能访问执行目标方法的参数了。

⑦访问目标方法的参数

访问目标方法最简单的做法是定义增强处理方法时将第一个参数定义为JoinPoint类型,当该增强处理方法被调用时,该JoinPoint参数就代表了织入增强处理的连接点。JoinPoint里包含了如下几个常用方法:
☞ Object[] getArgs():返回执行目标方法时的参数
☞ Signature getSignature():返回被增强的方法的相关信息
☞ Object getTarget():返回被织入增强处理的目标对象
☞ Object[] getThis:返回AOP框架为目标对象生成的代理对象

通过使用这些方法就可以访问到目标方法的相关信息

提示:ProceedingJoinPoint 是JoinPoint的子类型

@Component
@Aspect
public class FourAdviceTest {

    // 定义Around增强处理
    @Around("execution(* main.java.service.*.*(..))")
    public Object processTx(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("Around增强:执行目标方法之前,模拟开始事物...");
        // 访问执行目标方法的参数
        Object[] args = jp.getArgs();
        // 当执行目标方法的参数存在
        // 且第一个参数是字符串参数
        if (args != null && args.length > 0
                && args[0].getClass() == String.class) {
            // 改变第一个目标方法的第一个参数
            args[0] = "被改变的参数";
        }
        // 执行目标方法,并保存目标方法执行后的返回值
        Object rvt = jp.proceed(args);
        System.out.println("Around增强:执行目标方法之前,模拟结束事物...");

        return rvt + "新曾的内容";
    }

    // 定义Bfore增强处理
    @Before("execution(* main.java.service.*.*(..))")
    public void authority(JoinPoint jp) {
        System.out.println("Before增强:模拟执行权限检查");
        // 返回被织入增强处理的目标方法
        System.out.println("Before增强处理:被织入增强处理的目标方法为:"
                + jp.getSignature().getName());

        // 访问执行目标方法的参数
        System.out
                .println("Before增强:目标方法的参数为:" + Arrays.toString(jp.getArgs()));
        System.out.println("Before增强:被织入增强处理的目标对象为:" + jp.getTarget());
    }

    // 定义AfterReturning增强处理
    @AfterReturning(pointcut = "execution(* main.java.service.*.*(..))", returning = "rvt")
    public void log(JoinPoint jPoint, Object rvt) {
        System.out.println("AfterReturning增强:获取目标方法返回值:" + rvt);

        System.out.println("AfterReturning增强:模拟日志记录功能...");

        // 返回被织入增强处理的目标方法
        System.out.println("AfterReturning增强:被织入增强处理的目标方法为:"
                + jPoint.getSignature().getName());

        // 访问执行目标方法的参数
        System.out.println("AfterReturning增强:目标方法的参数为:"
                + Arrays.toString(jPoint.getArgs()));

        // 访问被增强处理的目标对象
        System.out.println("AfterReturning增强:被织入增强处理的目标对象为:"
                + jPoint.getTarget());
    }

    @After("execution(* main.java.service.*.*(..))")
    public void release(JoinPoint jp) {
        System.out.println("After增强:模拟方法结束后的释放资源...");
        // 被返回织入增强处理的目标方法
        System.out.println("After增强:被织入增强处理的目标方法为:"
                + jp.getSignature().getName());

        // 访问执行目标方法的参数
        System.out.println("After增强:目标方法的参数为:" + Arrays.toString(jp.getArgs()));

        // 访问被增强处理的目标对象
        System.out.println("After增强:被织入增强处理的目标对象为:" + jp.getTarget());
    }
}

从上面的代码可以看出,在Before、Around、AfterReturning、After4种增强处理中,其实都可以通过相同的代码来访问被增强的目标对象、目标方法和方法的参数,但只有Around增强处理可以改变方法参数。

Before、Around、AfterReturning、After的优先级顺序如下:
这里写图片描述

当不同的切面里的两个增强处理需要在同一个连接点被织入时,Spring AOP将以随机的顺序来织入这两个增强处理。如果应用需要指定不同切面类里增强处理的优先级,Spring提供了如下两种解决方案:
☞ 让切面类实现org.springframework.core.Ordered接口,实现该接口只需实现一个int getOrder()方法,该方法返回值越小,则优先级越高
☞ 直接使用@Order注解来修饰一个切面类,使用@Order时可以指定一个int型的value属性,该属性值越小,则优先级越高

注:优先级高的切面类里的增强处理的优先级总是比优先级低的切面类里的增强处理的优先级更高

同一个切面类里的两个相同类型的增强处理在同一个连接点被织入时,Spring AOP将以随机的顺序来织入这两个增强处理,没有办法指定他们的织入顺序。

如果只要访问目标方法的参数,Spring还提供了一种更简单的方法:我们可以在程序中使用args来绑定目标方法的参数。如果在一个args表达式中指定了一个或多个参数,则该切入点将只匹配具有对应形参的方法,且目标方法的参数值将被传入增强处理方法。

@Component
@Aspect
public class AccessArgAspect {

    // 下面的args(food,time)保证该切入点只匹配具有第一个参数是字符串,第二个数是 Date的方法
    @AfterReturning(pointcut = "execution(* main.java.service.*.*(..)) && args(food,time)", returning = "retVal")
    public void access(Date time, String food, Object retVal) {
        System.out.println("目标方法中String参数为:" + food);
        System.out.println("目标方法中Date参数为:" + time);
        System.out.println("模拟记录日志....");
    }
}

上面的程序中增加了&&args(food, time)部分,这意味着可以在增强处理方法中定义food、time两个形参——定义这两个形参时,形参类型可以任意指定,但一旦指定了这两个形参类型,则两个形参类型将用于限制该切入点只匹配第一个参数类型是String、第二个参数类型是Date的方法。

@Component
public class Chinese implements Person {

    @Override
    public String sayHello(String name) {
        return name + " Hello, Spring AOP";
    }

    public void eat(String food, Date time) {
        System.out.println("我正在吃:" + food + ",现在时间是:" + time);
    }
}

上面的类中包含两个方法,但只有eat()方法第一个参数类型是String、第二个参数类型是Date,所以Spring AOP将只对该方法织入access()增强处理。

输出结果为
Spring笔记——AOP(注解方式)_第1张图片

由结果可以看出,使用args表达式有如下两个作用:
☞ 提供了一种简单的方式来访问目标方法的参数
☞ 可用于对切入表达式增加额外的限制

除此之外,使用args表达式时还可以使用如下形式:args(name, age, ..),这表明增强处理方法中可通过name、age来访问目标方法的参数。注意表达式括号中的2个点,它表示可匹配更多参数——如果该args表达式对应的增强处理方法签名为:

@AfterReturning(pointcut = "execution(* main.java.service.*.*(..)) && args(name, age, ..)", returning = "retVal")
public void doSomething(String name, int age)

这意味着只要目标方法第一个参数是String类型、第二个参数是int类型,则该方法就可以匹配该切入点。

⑧定义切入点

所谓定义切入点,其实质就是为一个切入点表达式起一个名称,从而允许在多个增强处理中重用该名称。
Spring AOP只支持以Spring Bean的方法执行组作为连接点,所以可以把切入点看成所有能和切入点表达式匹配的Bean方法。

切入点定义包含两个部分:
☞ 一个切入点表达式
☞ 一个包含名字和任意参数的方法签名
其中切入点表达式用于指定该切入点和哪些方法进行匹配,包含名字和任意参数的方法签名将作为该切入点的名称

在@AspectJ风格的AOP中,切入点签名采用一个普通的方法定义(方法体通常为空)来提供,且该方法的返回值必须为void;切入点表达式需使用@Pointcut来标注。

下面的代码片段定义了一个切入点:anyOldTransfer,这个切入点将匹配任何名为transfer的方法执行。

@Pointcut("execution(* main.java.service.*.*(..))")
private void anyOldTransfer() {}

如果需要使用本切面类中的切入点,则可在使用注解时,指定value属性值为已有的切入点,如下面的代码片段所示:

@AfrerReturning(pointcut="myPointcut()", returning="retVal")
public void writeLog(String msg, Object retVal){
    ......
}

⑨切入点指示符

前面定义切入点表达式时大量使用了execution表达式,其中execution就是一个切入点指示符。Spring AOP仅支持部分AspectJ的切入点指示符,但Spring AOP还额外支持一个bean切入点指示符。
不仅如此,因为Spring AOP只支持使用方法调用作为连接点,所以Spring AOP的切入点指示符仅匹配方法执行的连接点。

Spring AOP一共支持如下几种切入点指示符:
☞ execution:用于匹配执行方法的连接点,这是Spring AOP中最主要的切入点指示符。该切入点的用法也相对复杂,execution表达式的格式如下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

上面格式中execution是不变的,用于作为execution表达式的开头,整个表达式中各部分的解释如下。
♦ modifiers-pattern:指定方法的修饰符,支持通配符,该部分可省略
♦ ret-type-pattern:指定方法返回值类型,支持通配符,可以使用“*”通配符来匹配所有返回值类型
♦ declaring-type-pattern:指定方法所属的类,支持通配符,该部分可省略
♦ name-pattern:指定匹配指定方法名,支持通配符,可以使用“*”通配符来匹配所有方法
♦ param-pattern:指定方法声明中的形参列表,支持两个通配符:“”和“..”,其中“”代表一个任意类型的参数,而“..”代表零个或多个任意类型的参数。例如,()匹配了一个不接受任何参数的方法,而(..)匹配了一个接受任意数量参数的方法(零个或更多),()匹配了一个接受一个任何类型参数的方法。(, String)匹配了一个接受两个参数的方法,第一个可以是任意类型,第二个则必须是String类型
♦ throws-pattern:指定方法声明抛出的异常,支持通配符,该部分可省略

☞ within:限定匹配特定类型的连接点,当使用Spring AOP的时候,只能匹配方法执行的连接点
☞ this:用于限定AOP代理必须是指定类型的实例,用于匹配该对象的所有连接点。当使用Spring AOP的时候,只能匹配方法执行的连接点
☞ target:用于限定目标对象必须是指定类型的实例,用于匹配该对象的所有连接点。当使用Spring AOP的时候,只能匹配方法执行的连接点
☞ args:用于对连接点的参数类型进行限制,要求参数类型是指定类型的实例。当使用Spring AOP的时候,只能匹配方法执行的连接点
☞ bean:用于指定只匹配指定Bean实例内的连接点,实际上只能使用方法执行作为连接点。定义Bean表达式时需要传入Bean的id或name,表示只匹配该Bean实例内的连接点。支持使用“*”通配符

⑩组合切入点表达式
Spring支持使用如上三个逻辑运算符来组合切入点表达式。
☞ &&:要求连接点同时匹配两个切入点表达式
☞ ||:只要连接点匹配任意一个切入点表达式
☞ !:要求连接点不匹配指定指定切入点表达式

你可能感兴趣的:(Spring)