首先,我们要先了解一下什么是AOP
AOP(面向切面编程)是一种编程范式,用于将横切关注点与核心业务逻辑分离开来。
我们开发软件时,有些功能在很多地方都需要使用,比如日志记录、安全控制等。传统的方式是在每个需要用到这些功能的地方都编写相应的代码,这样会导致代码重复和维护困难。AOP 提供了一种解决方案,它将这些通用功能从主要业务逻辑中分离出来,形成一个独立的模块。在需要使用这些功能的地方,可以通过简单的配置将这个模块应用到代码中。
简而言之,AOP 可以帮助我们更好地组织代码,提高代码的可维护性和重用性。它将通用功能从业务逻辑中抽离出来,让我们能够更加专注于核心业务逻辑的开发。
(就是在不改变原有代码的基础上,将公共的一部分功能抽离出来,在需要的地方,为代码增加新功能)
AOP由以下几个组成部分组成:
1、连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
在springAOP中,理解为方法的执行(可以为任意方法)(因为Spring AOP仅支持方法级别的连接点)
2、切入点(Pointcut):匹配连接点的式子
在springAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
一个具体方法:com.zq.dao包下的BookDao接口中的无参返回值的sav()方法
匹配多个方法:所有的save方法、所有的get开头的放法(通配符)所有Dao结尾的任意方法、所有带一个参数的方法。
3、通知(Advice):在切入点处执行的操作,也就是共性的功能
在springAOP中,功能最终以方法实现
4、通知类:定义通知的类,(包含多个不同的通知)
5、切面(Aspect):描述通知与切入点的对应关系。(使用@Aspect注解标识)
6、目标对象:原始功能去掉共性功能对应的类产生的对象,这种对象无法直接完成最终工作的,也是我们要添加共性功能的对象。
7、代理实现:目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现(一般我们自定义一个接口,将接口加入到目标对象(方法)中,使切入点对映接口)
使用spring boot代码实现:
1、创建一个spring boot项目,并导入aop的起步依赖:
<!--springboot集成Aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、定义一个切面类,在切面类中实现我们的AOP增强操作。加入@Aspect注解,定义为切面类,并在切面类中,实现通知和切入点的实现,并绑定它们之间的关系。
@Component @Aspect //切面类
我们这次实现三种常用的通知类型:
前置通知:@Before
顾明知意,就是在方法执行前,执行通知。
//通知,在方法执行之前 @Before(value = "pointcut1()") public void PrintTime(){ //当前系统时间 String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); System.out.println(format); System.out.println("前置通知"); }
这是一个前置通知,表明在方法执行之前,先执行功能(打印当前系统时间,并输出一句话)。它的切入点是方法pointcut1(),我们根据这个pointcut1()方法,来设置一个切入点表达式:
// 切入点表达式,使用接口形式 @Pointcut("@annotation(com.example.aoptest.aop.Aop)") public void pointcut1() {}
我自定义了一个接口Aop,用来作为切入点。这个接口什么都没写,只是定义了一些运行规则:
@Target(ElementType.METHOD) //作用在方法上 @Retention(RetentionPolicy.RUNTIME) //运行时有效 public @interface Aop { }
现在,我们就可以将接口定义在我们要增强的方法上了。如图:
@Aop public void save(){ System.out.println("save............."); }
正常我们这个方法,应该输出"save........",但是在加入了@Aop注解之后,增强的功能就可以使用了,现在相当于,先执行前置通知,再执行原本的操作。都执行完毕之后,这个save方法,才算执行完毕!
在test中就行测试,输出结果如下:
后置通知:@After:在方法运行之后才执行通知
//通知,在方法执行之后 @After("pointcut1()") public void printHello(){ System.out.println("后置通知,在方法运行完毕之后才执行。"); }
同样的,这个通知也绑定了切入点表达式pointcut1()。那么,这需要在需要添加功能的方法上加入@Aop注解,就可使用在这部分功能。(为了以示区分,我先将前置通知去掉)运行结果如下:
环绕通知:@Around:可以在方法执行的任意位置运作通知,也是我们最常用到的一种类型。
定义环绕通知的模板如下:
//通知,环绕 @Around("pointcut3()") public Object printObject(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕前"); Object proceed = pjp.proceed(); System.out.println("环绕后"); return proceed; }
首先,解释一下这个模板: @Around注解,定义这个通知为环绕通知。参数"pointcut3()"代表绑定的切入点表达式。
这里着重解释一下环绕通知运行的流程。
假设A方法引入了环绕通知B,那么程序执行到A方法时。会先跳转到环绕通知B中,先执行B中的代码,环绕通知B有一个参数ProceedingJoinPoint,这个参数很重要,里面有方法A的参数、返回值等。pjp.proceed()代表对这个参数进行放行。(pjp.proceed()的返回值Object proceed就是目标方法A的返回值,得到这个返回值以后,还可以对目标方法的返回值做进一步的处理。)放行之后,就可以执行A中的操作,A中的操作执行完之后,再返回来执行B中剩余的操作,最后返回一个值proceed ,就是这个方法A最终的返回值。
简单画个图:
细节:在执行pjp.proceed()方法时,一定要抛一个异常。因为pjp.proceed()表示执行目标方法,但是我们不知道目标方法有没有异常。通知只是给你做功能增强的,并不负责处理异常,所以,在执行pjp.proceed()方法放行时,强制你抛一个异常。
环绕通知的执行结果如下:
总结:使用AOP可以帮助我们做代码的无侵入式增强,将共性功能抽离出来做成通知,在需要时加入到目标方法中,减少了代码的耦合性。另外,通知是没有类型的,是一个功能的具体实现。只是在通知方法上加入相应的注解,就可以将该通知定义为不同的类型。本次,我讲解了三种常用的类型(前置、后置、环绕)还有更多的通知类型,大家可以自行试验。
可能之前有的人一直用SSM,使用spring boot有些不熟悉,在 Spring Boot 中加入 AOP 是不需要在启动类加任何注解的,因为在AOP的默认配置属性中,spring.aop.auto 属性默认是开启的,也就是说只要引入了 AOP 依赖后,默认已经增加了 @EnableAspectJAutoProxy。