Spring(4)-AOP使用细节

有了Spring(3)-AOP快速入手 - marigo - 博客园的学习,大体知道AOP的使用,接下来我们对AOP的细节进行展开。

AOP-切入表达式

  1. 作用:通过表达式定位一个或者多个连接点
    1. 连接点可以理解成我们要切入到哪个类的哪个具体方法
  2. 语法:execution([权限修饰符][返回值类型][简单类名/全类名][方法名]([参数列表]))
  3. 举例讲解
    1. 表达式1:execution(*com.example.AInterface.*(..))
      1. 第一个*表示任意修饰符及任意返回值
      2. 第二个*表示任意方法
      3. ..表示任意数量和任意类型的参数
      4. 如果目标类、接口、切面类在同一个包内,可以省略包名(简单类名)
      5. 该表达式的含义是切入AInterface接口中声明的所有方法
    2. 表达式2:execution(public *AInterface.*(..))
      1. 切入到AInterface接口中声明的所有public方法
    3. 表达式3:execution(public double AInterface.*(..))
      1. 切入到AInterface接口中返回double类型数值的方法
    4. 表达式4:execution(public double AInterface.*(doublue, ..))
      1. 切入到AInterface接口中返回double类型数值的方法,并且第一个参数要求是double类型
    5. 表达式5:execution(public double AInterface.*(doublue, double))
      1. 切入到AInterface接口中返回double类型数值的方法,并且参数类型要求是double,double
    6. 表达式6:execution(**.add(int,..))||execution(**.sub(int,..))
      1. 在AspectJ中,切入点表达式可以用&& || ! 等操作符结合起来
      2. 该表达式含义是任意类中第一个参数为int类型的add方法或者sub方法
  4. 注意事项
    1. 切入表达式也可以指向类的方法,这时切入表达式就会对该类(对象)生效。比如我们在上一期中用到的execution(public float com.example.aspectj.SmartDog.getSum(float ,float)),那么这意味着只会对SmartDog 这个类(对象)的getSum 方法切入。
    2. 切入表达式也可以指向接口的方法, 这时切入表达式会对实现了接口的类/对象生效。比如execution(public float com.example.aspectj.SmartAnimal.getSum(float ,float))SmartAnimal是接口,SmartDogSmartCat是其实现类,那么这个切入表达式会对SmartDogSmartCat都起作用。
    3. 切入表达式也可以对没有实现接口的类进行切入,不一定非要接口或接口实现类,表达式一样设置就好。
      1. 那么这种代理和前面讲的代理有区别吗?是有区别的,前面通过接口的代理是走的jdk的Proxy底层机制,而没有实现接口的代理是Spring的CGlib的底层机制。原理层面可以看:动态代理jdk的Proxy与spring的CGlib - 三只蛋黄派 - 博客园

AOP-JoinPoint

JoinPoint的作用是获取到调用方法的签名。
常用方法:

public void beforeMethod(JoinPoint joinPoint){  
    // 1. 获取目标方法名
    String methodName = joinPoint.getSignature().getName();  
    // 2. 获取目标方法的参数  
    Object[] args = joinPoint.getArgs();  
    // 3. 获取目标方法所属类的简单类名  
    String className = joinPoint.getSignature().getDeclaringType().getSimpleName();  
    // 4. 获取目标方法所属类的全类名  
    String targetName = joinPoint.getSignature().getDeclaringTypeName();  
    // 5. 获取目标方法声明类型(public、private等) 返回的是数字 
    int modifiers = joinPoint.getSignature().getModifiers();  
    // 6. 获取被代理的对象  
    Object target = joinPoint.getTarget();  
    // 7. 获取代理对象自己  
    Object aThis = joinPoint.getThis();  
}

AOP-返回通知获取结果

在此之前,模仿@Before可以写出@After的代码:

@AfterReturning(value = "execution(public float com.example.aspectj.SmartDog.getSum(float ,float))")  
public void showSuccessEndLog(JoinPoint joinPoint) {  
    System.out.println("返回通知");  
    Signature signature = joinPoint.getSignature();  
    // 2. 在调用目标方法之后打印“方法结束”日志  
    System.out.println("日志--方法名:" + signature.getName() + "--方法结束");  
}

查看@AfterReturning注解的源码,可以发现,有一个returning属性,这表示我们将来在执行目标方法的时候,把目标方法的结果赋值给returning所定义的参数中

@Retention(RetentionPolicy.RUNTIME)  
@Target({ElementType.METHOD})  
public @interface AfterReturning {  
    String value() default "";  
  
    String pointcut() default "";  
  
    String returning() default "";  
  
    String argNames() default "";  
}

比如,我们可以加上一个参数returning = "result",这意味着将来getSum(float ,float)的执行结果会赋值给result

@AfterReturning(value = "execution(public float com.example.aspectj.SmartDog.getSum(float ,float))" , 
                returning = "result")

如果要实现将目标方法getSum的返回结果赋值给切面方法showSuccessEndLog,那么returning = "result"定义的参数名要和Object result参数名一致,否则无法自动填充

@AfterReturning(value = "execution(public float com.example.aspectj.SmartDog.getSum(float ,float))",  
                returning = "result")  
public void showSuccessEndLog(JoinPoint joinPoint, Object result) {  
    System.out.println("返回通知");  
    Signature signature = joinPoint.getSignature();  
    // 2. 在调用目标方法之后打印“方法结束”日志  
    System.out.println("日志--方法名:" + signature.getName() + "--方法结束");  
}

总结一下传递流程:目标方法SmartDog.getSum(float ,float) 执行后会有一个返回值returning = "result",这个返回值赋给Object result,从而在切面方法showSuccessEndLog 中可以用result完成相应的业务逻辑。

AOP-异常通知中获取异常

我们在SmartDog 原来的基础上加入一个异常:result=1/0;

@Component  
public class SmartDog implements SmartAnimalable{  
    @Override  
    public float getSum(float i, float j) {  
        float result = i + j;  
        System.out.println("getSum() 方法内部打印 result= " + result);  
        result = 1/0;  // 模拟异常  
        return result;  
    }  
    @Override  
    public float getSub(float i, float j) {  
        float result = i - j;  
        System.out.println("getSub() 方法内部打印 result= " + result);  
        return result;  
    }  
}

我们要尝试获取这个算术异常,肯定要用到@AfterThrowing,那么具体怎么用呢,先看一下源码:

@Retention(RetentionPolicy.RUNTIME)  
@Target({ElementType.METHOD})  
public @interface AfterThrowing {  
    String value() default "";  
  
    String pointcut() default "";  
  
    String throwing() default "";  
  
    String argNames() default "";  
}

其中有一个属性是throwing,用来接收抛出的异常,规则跟@AfterReturning的returning要求是一样的,传递流程也是一样的,不具体展开了。

@AfterThrowing(value = "execution(public float com.example.aspectj.SmartDog.getSum(float ,float))",  
               throwing = "throwable")  
public void showEndLog(JoinPoint joinPoint, Throwable throwable) {  
    System.out.println("异常通知");  
    Signature signature = joinPoint.getSignature();  
    // 3. 在调用目标方法之后打印“方法结束”日志  
    System.out.println("异常:" + throwable);  
}

测试结果:

异常通知
异常:java.lang.ArithmeticException: / by zero

AOP-环绕通知

如果我们觉得前置通知、返回通知、异常通知、最终通知写起来还是太麻烦了,可以用环绕通知,它将四种通知整合到了一起。
补充一个知识点,如果我们有多个切面类,都对某个目标方法进行了切入,运行的时候不会起冲突,都会执行,比较好理解,只是单独提出来点一下。
继续环绕通知的代码,了解就好:

@Aspect  
@Component  
public class SmartAnimalAspect2 {  
    @Around(value = "execution(public float com.example.aspectj.SmartDog.getSum(float ,float))")  
    public Object doAround(JoinPoint joinPoint) {  
        Object result = null;  
        String methodName = joinPoint.getSignature().getName();  
        try {  
            //1.相当于前置通知完成的事情  
            Object[] args = joinPoint.getArgs();  
            List<Object> argList = Arrays.asList(args);  
            System.out.println("AOP 环绕通知--" + methodName + "方法开始了--参数有:" + argList);  
            //在环绕通知中一定要调用 joinPoint.proceed()来执行目标方法  
            result = joinPoint.proceed();  
            //2.相当于返回通知完成的事情  
            System.out.println("AOP 环绕通知" + methodName + "方法结束了--结果是:" + result);  
        } catch (Throwable throwable) {  
            //3.相当于异常通知完成的事情  
            System.out.println("AOP 环绕通知" + methodName + "方法抛异常了--异常对象:" + throwable);  
        } finally {  
            //4.相当于最终通知完成的事情  
            System.out.println("AOP 后置通知" + methodName + "方法最终结束了...");  
        }  
        return result;  
    }  
}

AOP-切入点表达式重用

在前面使用过程中,我们会发现在@Before(value = "execution(public float com.example.aspectj.SmartDog.getSum(float ,float))")@AfterReturning(value = "execution(public float com.example.aspectj.SmartDog.getSum(float ,float))", returning = "result"),切入的目标方法是相同的,是不是可以简化,这就引出了切入点表达式重用:为了统一管理切入点表达式
在切面类中,首先定义一个切入点表达式,在后面就可以直接使用:

@Pointcut("execution(public float com.example.aspectj.SmartDog.getSum(float ,float))")  
public void pointCut1() {}

接下来就可以直接使用了:

@Before(value = "pointCut1()")  
public void showBeginLog(JoinPoint joinPoint) {  
    System.out.println("前置通知");  
    Signature signature = joinPoint.getSignature();  
    // 1. 在调用目标方法之前打印“方法开始”日志  
    System.out.println("日志--方法名:" + signature.getName() + "--方法开始--参数:" + Arrays.asList(joinPoint.getArgs()));  
}

AOP-切面优先级

对于某个目标方法,有多个切面在同一个切入点切入,那么执行的优先级如何控制呢?
基本语法:@order(value=n),n越小,优先级越高

@Aspect  
@Order(value = 10)  
@Component  
public class SmartAnimalAspect {}

我们定义两个一样的切面类,如果不设置order,运行一个实例结果如下:

切面1的前置通知
切面2的前置通知
getSum() 方法内部打印 result= 3.0
切面2的返回通知
切面2的最终通知
切面1的返回通知
切面1的最终通知

现在设置切面1@Order(value = 2),切面2@Order(value = 1),结果如下:

切面2的前置通知
切面1的前置通知
getSum() 方法内部打印 result= 3.0
切面1的返回通知
切面1的最终通知
切面2的返回通知
切面2的最终通知

可以看出来,并不是设置优先级高就代表着它的所有切面方法都先执行

AOP-基于XML配置AOP

之前的例子都是通过注解配置AOP,其中也可以通过XML配置AOP。

<!--配置切面类 SmartAnimalAspect bean-->    <bean id="smartAnimalAspect" class="com.example.aspectj.SmartAnimalAspect"/>  
<!--配置实现类 SmartDog bean-->    <bean id="smartDog" class="com.example.aspectj.SmartDog"/>  
<!--AOP xml配置-->  
    <aop:config>  
        <!--配置切面,也就是统一切入点,同 pointCut()-->        <aop:pointcut id="smartDogPointcut" expression="execution(* com.example.aspectj.SmartDog.getSum(..))"/>  
        <!--配置切面方法-->  
        <aop:aspect ref="smartAnimalAspect" order="1">  <!--配置优先级,同order-->  
            <aop:before method="showBeginLog" pointcut-ref="smartDogPointcut"/>  
            <aop:after method="showEndLog" pointcut-ref="smartDogPointcut"/>  
            <aop:after-returning method="showReturnLog" pointcut-ref="smartDogPointcut" returning="result"/>  
        </aop:aspect>  
    </aop:config>
posted @ 2024-04-27 20:02  marigo  阅读(3)  评论(0编辑  收藏  举报