有了Spring(3)-AOP快速入手 - marigo - 博客园的学习,大体知道AOP的使用,接下来我们对AOP的细节进行展开。
AOP-切入表达式
- 作用:通过表达式定位一个或者多个连接点
- 连接点可以理解成我们要切入到哪个类的哪个具体方法
- 语法:
execution([权限修饰符][返回值类型][简单类名/全类名][方法名]([参数列表]))
- 举例讲解
- 表达式1:
execution(*com.example.AInterface.*(..))
- 第一个
*
表示任意修饰符及任意返回值 - 第二个
*
表示任意方法 ..
表示任意数量和任意类型的参数- 如果目标类、接口、切面类在同一个包内,可以省略包名(简单类名)
- 该表达式的含义是切入AInterface接口中声明的所有方法
- 第一个
- 表达式2:
execution(public *AInterface.*(..))
- 切入到AInterface接口中声明的所有public方法
- 表达式3:
execution(public double AInterface.*(..))
- 切入到AInterface接口中返回double类型数值的方法
- 表达式4:
execution(public double AInterface.*(doublue, ..))
- 切入到AInterface接口中返回double类型数值的方法,并且第一个参数要求是double类型
- 表达式5:
execution(public double AInterface.*(doublue, double))
- 切入到AInterface接口中返回double类型数值的方法,并且参数类型要求是double,double
- 表达式6:
execution(**.add(int,..))||execution(**.sub(int,..))
- 在AspectJ中,切入点表达式可以用
&&
||
!
等操作符结合起来 - 该表达式含义是任意类中第一个参数为int类型的add方法或者sub方法
- 在AspectJ中,切入点表达式可以用
- 表达式1:
- 注意事项
- 切入表达式也可以指向类的方法,这时切入表达式就会对该类(对象)生效。比如我们在上一期中用到的
execution(public float com.example.aspectj.SmartDog.getSum(float ,float))
,那么这意味着只会对SmartDog
这个类(对象)的getSum
方法切入。 - 切入表达式也可以指向接口的方法, 这时切入表达式会对实现了接口的类/对象生效。比如
execution(public float com.example.aspectj.SmartAnimal.getSum(float ,float))
,SmartAnimal
是接口,SmartDog
和SmartCat
是其实现类,那么这个切入表达式会对SmartDog
和SmartCat
都起作用。 - 切入表达式也可以对没有实现接口的类进行切入,不一定非要接口或接口实现类,表达式一样设置就好。
- 那么这种代理和前面讲的代理有区别吗?是有区别的,前面通过接口的代理是走的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>
标签:joinPoint,Spring,float,细节,result,AOP,方法,public,切面
From: https://www.cnblogs.com/marigo/p/18162436