Spring AOP

主要概念

  • aspect(切面) 由通知、切点、组成,是个逻辑概念可以理解为以下概念的集合

  • pointcut(切点) 每个类都拥有多个连接点,可以理解为连接点的集合。

  • joinpoint(连接点),程序执行的特定位置,如某个方法调用等

  • weaving(织入) 将通知(advice)连接到具体连接点的过程

  • advice(通知) 是植入到目标类连接点商店一段代码。

    通知种类
    • Before advice 前置通知 无论方法是否异常都会执行,因为是先执行的

    • After returning advice 后置通知

    • after throwing advice 异常通知

    • after finally advice 最终通知 无论目标方法是否异常都会执行

    • Around advice 环绕通知

      通过调用ProceedingJoinPoint.proceed()可以在目标执行全过程中执行。

  • target: 目标对象

  • aop Proxy: 产生的代理新对象

Spring AOP 底层实现,是通过JDK动态代理或CGLib代理在运行实际在对象初始化阶段织入代码的。

代码实现

定义切面类

定义一个Aspect切面

  • 增加 @Aspect接口

    当然需要注意注入@Component

  • 定义切点 pointcut

    在哪些地方执行,采用@pointcut注解完成,如@Pointcut(public * comm.xxx.xxx..(…))

    修饰符(可以不写,但不能用*) + 返回类型 + 哪些包下的类 + 哪些方法 + 方法参数 “*” 代表不限, “..” 两个点代表参数不限

    image-20220628142253004

  • 定义 Advice 通知

    通知的五种切点类型的注解@Befor @After @AfterReturing @AfterThrowing @Around 来完成切点的增强动作 如@Before(“myPointcut()”)

    image-20220628142504318

    只有在Around(环绕通知时才可以用)proceedingJoinPoint,其他情况下使用会报错

JDK代理的使用

代理的原理很简单,就是我们调用一个类时通过它的代理类去调用,这个类可以在执行目标类的前后执行我们想织入的代码。

但我们没都手写一个代理类会非常复杂而且具有倾入性,所以我们可以采用JDK或CGLib的方法来进行代理。

dk中为实现代理提供了支持,主要用到2个类:

1
2
java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler

jdk自带的代理使用上面有个限制,只能为接口创建代理类,如果需要给具体的类创建代理类,需要用后面要说的cglib

  • Proxy

    这是JDK动态代理中主要的可一个类。

    • getProxyClass

      为指定接口黄健代理的Class对象

      1
      2
      3
      4
      public static Class<?> getProxyClass(ClassLoader loader,
      Class<?>... interfaces)
      // ClassLoader 类的加载器,由于需要创建一个新的类,这个新类会在这里被加载到MetaSpace中
      // 返回的是一个类
    • newProxyInstance

      创建代理的实例对象

      1
      2
      3
      public static Object newProxyInstance(ClassLoader loader,
      Class<?>[] interfaces,
      InvocationHandler h)

      这个方法先为指定的接口创建代理类,然后会生成代理类的一个实例,最后一个参数比较特殊,是InvocationHandler类型的,这个是个借口如下:

      1
      2
      public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable;

      上面方法会返回一个代理对象,当调用代理对象的任何方法的时候,会就被InvocationHandler接口的invoke方法处理,所以主要代码需要卸载invoke方法中,稍后会有案例细说。

    • getInvocationHandler

      获取代理对象的InvocationHandler对象

      1
      2
      public static InvocationHandler getInvocationHandler(Object proxy)
      throws IllegalArgumentException

      *上面几个方法大家熟悉一下,下面我们来看创建代理具体的2种方式。

创建代理方式一

步骤

  1. 创建代理类

    调用Proxy.getProxyClass方法获取代理类的Class对象

  2. 创建请求处理器

    使用InvocationHandler接口创建代理类的处理器

  3. 创建代理对象

    通过代理类和InvocationHandler创建代理对象

  4. 使用代理对象

    上面已经创建好代理对象了,接着我们就可以使用代理对象了

案例

创建IService接口的代理对象

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

public class CostTimeInvocationHandler implements InvocationHandler {

private Object target;

public CostTimeInvocationHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long starTime = System.nanoTime();
Object result = method.invoke(this.target, args);//@1
long endTime = System.nanoTime();
System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
return result;
}

/**
* 用来创建targetInterface接口的代理对象
*
* @param target 需要被代理的对象
* @param targetInterface 被代理的接口
* @param <T>
* @return
*/
public static <T> T createProxy(Object target, Class<T> targetInterface) {
if (!targetInterface.isInterface()) {
throw new IllegalStateException("targetInterface必须是接口类型!");
} else if (!targetInterface.isAssignableFrom(target.getClass())) {
throw new IllegalStateException("target必须是targetInterface接口的实现类!");
}
return (T) Proxy.newProxyInstance(

target.getClass().getClassLoader(),
//同目标类的ClassLoader
target.getClass().getInterfaces(),
//接口
new CostTimeInvocationHandler(target));
//这里传入了target,并且在处理器中执行了target的相应方法
//这个类中所有的方法都会在 invoke(Object proxy, Method method, Object[] args)中执行
}
}

CGLB 动态代理

CGLib是Spring实现动态代理的工具类,许多Spring的

CGlib可以产生代理对象,创建一个代理类。

通常用法

image-20220629002946695

使用CGLIB创建动态代理时会在target中创建一个代理对象