# 前言

因为 AOP 大致是什么,第一篇已经讲过了,这里放一张图:

AOP 的理念:就是将分散在各个业务逻辑代码中相同的代码通过横向切割的方式抽取到一个独立的模块中!

导入依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.13</version>
</dependency>

# AOP 配置方式

AOP 配置分为 XML 配置和基于 @AspectJ 配置

AspectJ 是 java 实现的 AOP 框架,能对 Java 代码进行 AOP 编译,让其具有 AspectJ 的 AOP 功能。

# XML 配置

  • 定义目标类
public class Student {
    private String name;
    
    public String study() {
    	System.out.println(name + "is studying...");
        return name;// 这个返回值,只是为了之后演示后置通知
    }
}

我们希望在目标类调用 study 时进行一些操作(可能是调用前,可能是调用后)

  • 定义切面类(操作抽象出切面类),之前学了 JUL,可以用,这里为了方便就是用 println
// 名字随便定义,之后配置到 xml 文件中需要一致
public class LogAspect {
    // 环绕通知
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知,进入方法");
        Object o = pjp.proceed();
        System.out.println("环绕通知,退出方法");
        return o;
    }
    
    // 前置通知
    public void before() {
        System.out.println("前置通知");
    }
    
    // 后置通知
    public void afterReturning(String result) {
        System.out.println("后置通知, 返回值: " + result);
    }
	
    // 异常通知
    public void afterThrowing(Exception e) {
        System.out.println("异常通知, 异常: " + e.getMessage());
    }
    
    // 最终通知
    public void after() {
        System.out.println("最终通知");
    }
}
  • XML 文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd                    
                           http://www.springframework.org/schema/aop 
                           http://www.springframework.org/schema/aop/spring-aop.xsd">

然后就是将两个类注册为 bean

<bean name="student" class="com.bean.Student"/>
<bean name="aopTest" class="com.aop.LogAspect"/>

通过 aop:config 添加新的 AOP 配置:

<aop:config>
    <!-- 之后所有 aop 切面类都在这里面 -->
</aop:config>

通过例子演示一下:

<aop:aspect ref="logAspect">
    <!-- 配置切入点 -->
	<aop:pointcut id="study" expression="execution(* com.bean.Student.study())" />
    <!-- 环绕通知 -->
    <aop:around method="around" pointcut-ref="study"/>
    <!-- 前置通知 -->
    <aop:before method="before" pointcut-ref="study"/>
    <!-- 后置通知;returning 属性:用于设置后置通知的第二个参数的名称,类型是 Object -->
    <aop:after-returning method="afterReturning" pointcut-ref="study" returning="result"/>
    <!-- 异常通知:如果没有异常,将不会执行增强;throwing 属性:用于设置通知第二个参数的的名称、类型 -->
    <aop:after-throwing method="afterThrowing" pointcut-ref="study" throwing="e"/>
    <!-- 最终通知 -->
    <aop:after method="after" pointcut-ref="study"/>
</aop:aspect>

这里几个补充知识:

  1. 切入点就是要触发执行操作的地方,应该很好理解吧
  2. 前置,后置,最终啥的,都是人为定义的,标准就是这么称呼,我理解的是定义了操作的执行顺序,你要喜欢也可以自己取名字。只不过在 xml 中配置也要遵循标准。
  3. 切入点的 execution 的格式为:修饰符 包名。类名。方法名称 (方法参数)
  4. 修饰符:public、protected、private、包括返回值类型、static 等等(使用 * 代表任意修饰符)。
  5. 环绕方法相当于完全代理了此方法,通过 ProceedJoinPoint 执行代理方法。
  6. 环绕方法代理的切入点如果有参数,直接使用 pjp.proceed() 不会改变传入的参数,但是也可以自己更改参数: pjp.proceed(new Object[]{arg1,arg2...})
  7. 如果你想在前置,后置等方法拿到切入点的信息,可以传入 JoinPoint
public void before(JoinPoint p) {
    // .... 
}

最后执行一下,我在 Student 的 bean 中配置的 namecyan ,所以最后结果为:

前置通知
环绕通知,进入方法
cyan is studying...
环绕通知,退出方法
后置通知, 返回值: cyan
最终通知

将打印语句换成日志语句,就实现了切面日志。

# 接口实现

我不是很喜欢这种,要说为什么,emm,一个类实现太多接口太丑了?hhhh

就是有这样的接口: MethodBeforeAdviceAfterReturningAdvice 等,分别对应前置通知,最终,后置等,需要哪种通知,切面类就实现哪种接口。

public class AopTest implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("通过Advice实现AOP");
    }
}

args 代表的是方法执行前得到的实参列表,还有 target 表示执行此方法的实例对象。

在 xml 配置文件中的写法(假设注册 bean 对象的 id 是 aoptest ):

<aop:config>
    <aop:pointcut id="stu" expression="execution(* com.bean.Student.say(String))"/>
    <aop:advisor advice-ref="aoptest" pointcut-ref="stu"/>
</aop:config>

# 补充术语

没有一开始就抛出术语,因为我当时学的时候,看到一摩尔的术语,头都大了,还看不懂。

  • 通知(Advice): AOP 框架中的增强处理,通知描述了切面何时执行以及如何执行增强处理,也就是我们上面编写的方法实现。

  • 连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出,实际上就是我们在方法执行前或是执行后需要做的内容。

  • 切点(PointCut): 可以插入增强处理的连接点,可以是方法执行之前也可以方法执行之后,还可以是抛出异常之类的。

  • 切面(Aspect): 切面是通知和切点的结合,我们之前在 xml 中定义的就是切面,包括很多信息。

  • 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。

  • 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,我们之前都是在将我们的增强处理添加到目标对象,也就是织入(这名字挺有文艺范的)

# AspectJ 配置

基于 xml 配置,我们需要编写较多代码,并且存在一些不足(我也不知道是啥不足),所以 spring 使用了 @AspectJ 为 AOP 实现提供一套注解。

Spring 小组喜欢 @AspectJ 注解风格更胜于 Spring XML 配置。

考虑到本篇文章篇幅已经够多了,而且 AspectJ 是和注解相关的,所以就留到下一篇文章讲解。

# 参考

https://pdai.tech/md/spring/spring-x-framework-aop.html

https://www.yuque.com/qingkongxiaguang/spring/rlgcf7#382e795a