Spring-AOP

AOP:Aspect Oriented Programming,面向切面编程。

OOP:Object Oriented Programming,面向对象编程。

面向切面编程:基于OOP基础之上的新的编程思想,指在程序运行期间,将某段代码(日志)动态的切入(不把日志代码写死在业务逻辑方法中)到指定方法指定位置(方法的开始、结束、异常位置)进行运行的这种编程方式。


可以用动态代理来实现面向切面编程,但是有缺点:

写起来比较麻烦,每次都要通过反射来创建代理对象;jdk默认的动态代理如果目标对象没有实现任何接口,是无法为他创建代理对象的。因为代理对象和被代理对象的纽带就是接口类型。


AOP专业术语:


抽取可重用的切入点表达式:

  1. 随便声明一个没有实现的返回void的空方法

  2. 给方法上标注@Pointcut注解

  3. 括号内写上切入点表达式

    切入点表达式结构execution(访问权限符 返回值类型 方法全类名(参数列表))

* :只能匹配一层路径/一个参数

execution(public int com.vickkkyz.aoppractice.math.MyMathCalculator.*(*,*))

.. :用在参数表,表示匹配任意多个参数,任意参数类型

用在路径上,表示多层路径,下面的表示aoppractice包下的多层路径下名字叫MyMathCalculator的类

execution(public int com.vickkkyz.aoppractice..MyMathCalculator.*(*,*))

1
2
3
// 下列切入点表达式的意思是切 MyMathCalculator类下的任意名字的方法,两个任意类型的参数
@Pointcut("execution(public int com.vickkkyz.aoppractice.math.MyMathCalculator.*(*,*))")
public void myPoint(){}

写一次,之后全引用即可。


通知方法的执行顺序

1
2
3
4
5
6
7
8
9
try{
@Before // 前置通知
method.invoke(obj,args);
@AfterReturning // 返回通知。正常返回
}catch(){
@AfterThrowing // 异常通知。方法异常
}finally{
@After // 后置通知
}

方法正常执行:@Beofre -> @After -> @AfterReturning

方法异常执行:@Before -> @After -> @AfterThrowing


使用参数JoinPoint可以拿到运行的方法的相信信息。需要在通知方法的参数列表上写上

JoinPoint joinPoint

在通知方法注解上写上throwing = "exception",参数列表上加上这个参数Exception exception,通知方法就可以接收到方法出现的异常信息,注解上加上returning = "result" ,参数上写上Object result,就可以获得方法的返回值。


Spring对增强方法要求不严格,因为通过注解标记了方法,只要能正常调用就完成了增强的目的;所以唯一要求严格的就是参数表,反射调用的时候得给参数赋值;JoinPoint是预定义的;如果有其他参数(接返回值的,接异常的),得声明出来哪个参数接什么;


@Around

环绕通知,是Spring中最强大的通知,环绕通知是上面四个通知的整合,四合一。

有一个强大的回调参数,可以拿到底层动态代理的invoke权,

如果使用环绕通知注解,那么目标方法的调用就是在@Around注解修饰的方法中,需要我们显示去利用动态代理调用目标方法,其实就相当于之前的Spring帮我们调用的method.invoke(obj,args) ,变为我们自己调用point.proceed(args); 最后将方法的返回值,即proceed返回出去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@Component
@Aspect
public class LogUtils {

@Pointcut("execution(public int com.vickkkyz.aoppractice.math.MyMathCalculator.*(int,int))")
public void myPoint(){}

@Before("myPoint()")
public void logBefore(JoinPoint joinPoint){
// 获取到方法运行的参数列表
Object[] args = joinPoint.getArgs();
// 方法签名
Signature signature = joinPoint.getSignature();
// 方法名称
String name = signature.getName();
System.out.println("[前置通知]方法执行前,参数为"+ Arrays.asList(args)+"方法名为:" + name);
}

@After("myPoint()")
public void logAfter(){
System.out.println("[后置通知]finally");
}

@AfterReturning(value = "myPoint()",returning = "result")
public void logAfterReturning(Object result){
System.out.println("[正常返回]try语句块结束,结果为" + result);
}

@AfterThrowing(value = "myPoint()", throwing = "exception")
public void logAfterThrowing(Exception exception) {
System.out.println("[方法异常]throwing,异常信息是");
exception.printStackTrace();
}

@Around("myPoint()")
public Object logAround(ProceedingJoinPoint point) throws Throwable {
Object[] args = point.getArgs();
Object proceed = null;
try{
System.out.println("环绕前置");
// 就是反射调用方法
proceed = point.proceed(args);
System.out.println("环绕正常返回");
}catch (Exception e){
System.out.println("环绕异常");
}finally {
System.out.println("环绕后置");
}
// 将方法的结果返回,最终程序获取的该方法的结果就是这个
// 如果在这里篡改了结果,那么方法的返回结果就一直是这个
return proceed;

}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class MyMathCalculator {

public int add(int a, int b) {
return a + b;
}

public int sub(int a, int b) {
return a - b;
}

public int mul(int a, int b) {
return a * b;
}

public int dev(int a, int b) {
return a / b;
}

}

AOP使用场景:

1)、AOP加日志保存到数据库;

2)、AOP做权限验证;

3)、AOP做安全检查;

4)、AOP做事务控制;

https://www.jianshu.com/p/9093e6ca3378


对切入点表达式格式的说明:(即@Point注解内的表达式)

within:匹配包/类型

1
2
3
4
5
// 匹配ProductService类种的所有方法
@Poincut("within(com.hhu.service.ProductService)")

//匹配com.hhu包及其子包下所有类的方法
@Pointcut("within(com.hhu..*)")

this、target、bean匹配对象

1
2
3
4
5
6
7
8
//匹配AOP对象的目标对象为指定类型的方法,即DemoDao的aop的代理对象
@Pointcut("this(com.hhu.DemaoDao)")

//匹配实现IDao接口的目标对象(而不是aop代理后的对象,这里即DemoDao的方法)
@Pointcut("target(com.hhu.Idao)")

//匹配所有以Service结尾的bean中的方法
@Pointcut("bean(*Service)")

@annotation:匹配注解

1
2
3
4
5
6
7
8
9
10
11
//匹配方法标注有AdminOnly注解的方法
@Pointcut("annotation(com.hhu.demo.security.AdminOnly)")

//匹配标注有Beta的类下的方法,要求annotation的RetentionPplicy级别为CLASS
@Pointcut("@within(com.google.common.annotations.Beta)")

//匹配标注有Repository类下的方法,要求的annotation的RetentionPolicy级别为RUNTIME
@Pointcut("@target(org.springframework.stereotype.Repository)")

//匹配传入的参数类型标注有Repository注解的方法
@Pointcut("@args(org.springframework.stereotype.Repository)")

https://blog.csdn.net/jaryle/article/details/88764751


Spring-AOP
https://vickkkyz.fun/2022/10/30/Java/framework/AOP/
作者
Vickkkyz
发布于
2022年10月30日
许可协议