目录

AOP

# AOP

AOP 面向切面编程,旨在不影响源代码的情况下为源代码添加额外功能,类似于Python的函数装饰器

# 准备工作

依赖

<dependencies>  
  <dependency>    
	  <groupId>org.springframework</groupId>  
	  <artifactId>spring-context</artifactId>  
      <version>5.3.20</version>  
  </dependency>  
  <dependency>    
	  <groupId>org.aspectj</groupId>  
      <artifactId>aspectjweaver</artifactId>  
      <version>1.9.9.1</version>  
  </dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12

思想 这是原本的功能类

@Component  
public class BookDaoImpl implements BookDao {  
    @Override  
    public void save() {  
        System.out.println("tommy");  
    }  
  
    @Override  
    public void update() {  
        System.out.println("lucky");  
    }  
}
1
2
3
4
5
6
7
8
9
10
11
12

现在,我希望在使用update方法时,会额外输出一句"hello"

首先我们要在SpringConfig.java中加入注释@EnableAspectJAutoProxy,这句话用意是为了告知配置类我们配置了AOP,记得扫描一下。

然后另开一个新类用于配置AOP

// MyAdvice.java
@Component  
@Aspect  
public class MyAdvice {  
  
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")  
    private void pt(){}  
  
    @After("pt()")  
    public void method(){  
        System.out.println("hello work");  
    }  
}
1
2
3
4
5
6
7
8
9
10
11
12
13

说明一下,首先@Aspect是为了对应SpringConfig中@EnableAspectJAutoProxy,告知SpringConfig扫描时识别到这个类是AOP的。

method()方法是我们真正想实现的功能方法,上面的@After("pt()")是指该方法在pt方法运行后执行,但实际上pt方法只作为空壳而已,即使你写了内容在pt里也不会执行。@Pointcut("execution(void com.itheima.dao.BookDao.update())")是声明pt方法仅作为断点使用,当执行void com.itheima.dao.BookDao.update()方法后,会触发pt断点,从而实现了AOP功能

# AOP工作流程

  1. Spring容器启动
  2. 读取所有切面配置中的切入点(读取使用的,未使用的不读取)
  3. 初始化bean,判定bean对应的类中的方法是否有匹配到任何切入点
    • 匹配失败:创建对象
    • 匹配成功:创建原始对象的代理对象
  4. 获取Bean执行方法
    • 获取Bean,调用方法并执行,完成操作
    • 获取的Bean是代理对象时,根据代理对象的运行模式运行原始方法与增强内容,完成操作 可能你对于上面这段流程比较困惑,以下是代码例子:
public static void main(String[] args) {  
    ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);  
    BookDao bd = ac.getBean(BookDao.class);  
    System.out.println(bd);  
}
1
2
3
4
5
@Component  
@Aspect  
public class MyAdvice {  
	//这是切入点表达式
    @Pointcut("execution(void com.itheima.dao.BookDao.update2())")// 注意是update2,实际没有这个方法  
    private void pt(){  
    }  
  
    @After("pt()")  
    public void method(){  
        System.out.println("hello work");  
    }  
}
1
2
3
4
5
6
7
8
9
10
11
12
13

我们只关注bd的实例类是什么,当打印bd时,结果是: com.itheima.dao.impl.BookDaoImpl@1f9f6368 这看上去好像没啥区别,当你System.out.println(bd.getClass());打印时会发现,结果依旧是class com.itheima.dao.impl.BookDaoImpl,并没啥变化,这是因为在第三步时匹配失败了,并不存在update2()方法,因此创建对象。

但当你将update2()改回update()时(update方法是实际存在的),再次打印,结果发生了改变。

com.itheima.dao.impl.BookDaoImpl@1f9f6368
class jdk.proxy2.$Proxy19
1
2

你会发现,实际上创建的Proxy对象了,因为update()这个方法是的的确确存在的

  • 目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的。
  • 代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现

# 切入点表达式

  • 切入点:要增强的方法
  • 切入点表达式:要增强的方法的描述方式

![[Pasted image 20220613194847.png]] ![[Pasted image 20220613194902.png]]

# 额外代码的运行时机

上面的案例中,你可能已经注意到了,我们在method()方法上加了一个@After注解,不难猜出肯定还有个@Before这俩都是控制额外代码的运行时机的。 但问题是,倘若我们希望额外运行的代码同时在前和后该怎么办呢??

@Around:

@Around("pt()")
public void around(ProceedingJoinPoint pjp) throws Throwable{
	System.out.println("前面代码");
	pjp.proceed();
	System.out.println("后面代码");
}
1
2
3
4
5
6

这样你就能实现包裹了,切记参数必须包含ProceedingJoinPoint pjp,不然程序压根不知道你原本的代码该放在哪个位置。

![[Pasted image 20220613201622.png]]

@AfterReturning: 当且仅当没有抛异常正常结束函数运行时才会执行 @AfterThrowing: 当且仅当抛异常时才会执行

# 取参

我们该如何获取到原本函数中的参数呢? Spring为我们提供了JoinPoint接口,JoinPoint接口拥有一个方法getArgs()用于获取原本函数的参数,同样的ProceedingJoinPoint作为JoinPoint的子接口,也继承了这一方法。取参除了查看传入参数外,还能用来修改参数。 假设萌新没看参数需求,胡乱传入了一个String类型的参数(实际该传入int),那么我们就能通过切面实现篡改参数。 例如判断参数类型是否为int类型,如不是则使用默认值0代替原本不合规的参数,然后再调用

Object[] a = new Object[]{1,2};  
jp.proceed(a);
1
2

如此一来,原函数接收到的参数就是你修正后的参数了,增强了程序健壮

最近更新
01
基本知识
07-18
02
卷积神经网络识别图像
07-18
03
损失函数
07-18
更多文章>