【Spring】Spring AOP

2023-12-15 22:40:20

AOP概述

什么是AOP

AOP(面向切面编程)是一种编程思想和技术,旨在将横切关注点与核心业务逻辑相分离,以提高代码的可维护性、可重用性和可扩展性。

在传统的面向对象编程中,我们将功能模块封装成类,并通过对象的方法调用来实现特定的功能。但有些功能可能会跨越多个对象和方法,包括日志记录、安全控制、事务管理等,这些功能通常不属于核心业务逻辑,但需要在多个地方重复使用。

AOP的核心思想是将这些横切关注点从业务逻辑中剥离出来,形成一个独立的模块,称为切面(Aspect)。切面定义了在哪些地方(连接点)以及如何(通知类型)将其横切逻辑织入到目标对象中。通过将切面织入到目标对象的过程,可以在运行时动态地改变目标对象的行为,而无需修改目标对象的源代码。

AOP是?种思想, 是对某?类事情的集中处理.

AOP的优点在于它可以提高代码的模块化和可重用性,减少代码的冗余和重复。通过将横切关注点与核心业务逻辑分离,使得代码更加清晰、可维护,并且易于扩展新的功能模块。

Spring AOP快速入门

学习什么是AOP后, 我们先通过下?的程序体验下AOP的开发, 并掌握Spring中AOP的开发步骤

需求: 统计图书系统各个接??法的执?时间.

1.引入AOP依赖

在pom.xml?件中添加配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. 编写AOP程序

package com.example.springaop.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class TimeAspect {
    /**
     * 记录?法耗时
     */
    @Around("execution(* com.example.springaop.controller.*.*(..))")
    public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
        //记录?法执?开始时间
        long begin = System.currentTimeMillis();
        //执?原始?法
        Object result = pjp.proceed();
        //记录?法执?结束时间
        long end = System.currentTimeMillis();
        //记录?法执?耗时
        log.info(pjp.getSignature() + "执?耗时: {}ms", end - begin);
        return result;
    }
}

package com.example.springaop.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {

    @RequestMapping("/t1")
    public String sayHi(){
        return "hi";
    }
}

在这里插入图片描述
对程序进行简单解释

@Aspect 表示这是一个切面类
@Around 环绕通知 在目标方法前后都会执行 后面的表达式表示对那些方法进行增强
ProceedingJoinPoint.proceed() 让原始?法执?

在这里插入图片描述

**此时我们并没有对controller中的代码做任何修改 但是确实完成了对接口执行时间的计算 **

通过上?的程序, 我们也可以感受到AOP?向切?编程的?些优势:
? 代码?侵?: 不修改原始的业务?法, 就可以对原始的业务?法进?了功能的增强或者是功能的改变
? 减少了重复代码
? 提?开发效率
? 维护?便

Spring AOP 详解

Spring AOP 核心概念

切点(Pointcut)

切点(Pointcut), 也称之为"切?点"

Pointcut 的作?就是提供?组规则 (使? AspectJ pointcut expression language 来描述), 告诉程序对
哪些?法来进?功能增强.
在这里插入图片描述
上面的表达式就是切点表达式

连接点(Join Point)

满?切点表达式规则的?法, 就是连接点. 也就是可以被AOP控制的?法

以??程序举例, 所有 com.example.springaop.controller路径下的?法, 都是连接点.

切点和连接点的关系
连接点是满?切点表达式的元素. 切点可以看做是保存了众多连接点的?个集合.
?如:
切点表达式: 全体教师
连接点就是: 张三,李四等各个?师

通知(Advice)

通知就是具体要做的?作, 指哪些重复的逻辑,也就是共性功能(最终体现为?个?法)

?如上述程序中记录业务?法的耗时时间, 就是通知

在这里插入图片描述
在AOP?向切?编程当中, 我们把这部分重复的代码逻辑抽取出来单独定义, 这部分代码就是通知的内容.

切面(Aspect)

切?(Aspect) = 切点(Pointcut) + 通知(Advice)

通过切?就能够描述当前AOP程序需要针对于哪些?法, 在什么时候执?什么样的操作

切?既包含了通知逻辑的定义, 也包括了连接点的定义.

切?所在的类, 我们?般称为切?类(被@Aspect注解标识的类)

通知类型

Spring中AOP的通知类型有以下几种

@Around: 环绕通知, 此注解标注的通知?法在?标?法前, 后都被执?
@Before: 前置通知,此注解标注的通知?法在?标?法前被执?
@After: 后置通知, 此注解标注的通知?法在?标?法后被执?, ?论是否有异常都会执?
@AfterReturning: 返回后通知, 此注解标注的通知?法在?标?法后被执?, 有异常不会执?
@AfterThrowing:异常后通知, 此注解标注的通知?法发?异常后执?

编写测试代码

package com.bite.book.aspect;

import lombok.extern.slf4j.Slf4j;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class TestAspect {

    @Pointcut("execution(* com.bite.book.controller.*.*(..))")
    private void pt(){};

    @Before("pt()")
    public void doBefore(){
        log.info("执行before方法");
    };

    @After("pt()")
    public void doAfter(){
        log.info("执行after方法");
    };

    @AfterReturning("pt()")
    public void AfterReturning(){
        log.info("执行afterReturn 方法");
    };

    @AfterThrowing("execution(* com.bite.book.controller.*.*(..))")
    public void doAfterThrowing(){
        log.info("执行afterThrowing 方法");
    };

    @Around("execution(* com.bite.book.controller.*.*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("Around方法开始执行");
        Object result = joinPoint.proceed();
        log.info("Around 方法结束执行");
        return result;
    };

}

package com.bite.book.controller;

import com.bite.book.aspect.MyAspect;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/aspectTest")

@RestController
public class AspectController {
    @MyAspect
    @RequestMapping("/t1")
    public String t1() {
        return "t1";
    }
    @MyAspect
    @RequestMapping("/t2")
    public boolean t2() {
        int a = 10 / 0;
        return true;
    }
}

在这里插入图片描述
正常情况下 @AfterThrowing 标识的通知方法不会执行

从上图也可以看出来, @Around 标识的通知?法包含两部分, ?个"前置逻辑", ?个"后置逻辑".其中**“前置逻辑” 会先于 @Before 标识的通知?法执?, “后置逻辑” 会晚于 @After 标识的通知?法执?**

当抛出异常时
在这里插入图片描述

此时@AfterReturning标识的通知方法不会执行 @AfterThrowing标识的通知方法执行了
@Around 环绕通知中原始方法调用时有异常, 通知中的环绕后的代码逻辑也不会再执行了 因为原方法调用出现异常了

注意事项

@Around 环绕通知需要调? ProceedingJoinPoint.proceed() 来让原始?法执?, 其他通知不需要考虑?标?法执?
@Around 环绕通知?法的返回值, 必须指定为Object, 来接收原始?法的返回值, 否则原始?法执?完毕, 是获取不到返回值的.
?个切?类可以有多个切点

@PointCut

上?代码存在?个问题, 就是存在?量重复的切点表达式 execution(* com.example.demo.controller..(…)) , Spring提供了 @PointCut 注解, 把公共的切点表达式提取出来, 需要?到时引?该切?点表达式即可.

在这里插入图片描述
当切点定义使?private修饰时, 仅能在当前切?类中使?, 当其他切?类也要使?当前切点定义时, 就需要把private改为public. 引??式为: 全限定类名.?法名()

切面优先级@Order

当我们在?个项?中, 定义了多个切?类时, 并且这些切?类的多个切?点都匹配到了同?个?标?法.当?标?法运?的时候, 这些切?类中的通知?法都会执?, 那么这?个通知?法的执?顺序是什么样的呢?

测试代码

@Slf4j
@Component
@Aspect
public class TestAspect {

    @Pointcut("execution(* com.bite.book.controller.*.*(..))")
    private void pt(){};

    @Before("pt()")
    public void doBefore(){
        log.info("执行before方法");
    };

    @After("pt()")
    public void doAfter(){
        log.info("执行after方法");
    };

//    @AfterReturning("pt()")
//    public void AfterReturning(){
//        log.info("执行afterReturn 方法");
//    };
//
//    @AfterThrowing("execution(* com.bite.book.controller.*.*(..))")
//    public void doAfterThrowing(){
//        log.info("执行afterThrowing 方法");
//    };
//
//    @Around("execution(* com.bite.book.controller.*.*(..))")
//    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
//        log.info("Around方法开始执行");
//        Object result = joinPoint.proceed();
//        log.info("Around 方法结束执行");
//        return result;
//    };

}

@Slf4j
@Component
@Aspect
public class TestAspect2 {

    @Pointcut("execution(* com.bite.book.controller.*.*(..))")
    private void pt(){};

    @Before("pt()")
    public void doBefore(){
        log.info("执行before方法");
    };

    @After("pt()")
    public void doAfter(){
        log.info("执行after方法");
    };

//    @AfterReturning("pt()")
//    public void AfterReturning(){
//        log.info("执行afterReturn 方法");
//    };
//
//    @AfterThrowing("execution(* com.bite.book.controller.*.*(..))")
//    public void doAfterThrowing(){
//        log.info("执行afterThrowing 方法");
//    };
//
//    @Around("execution(* com.bite.book.controller.*.*(..))")
//    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
//        log.info("Around方法开始执行");
//        Object result = joinPoint.proceed();
//        log.info("Around 方法结束执行");
//        return result;
//    };

}
@Slf4j
@Component
@Aspect
public class TestAspect3 {

    @Pointcut("execution(* com.bite.book.controller.*.*(..))")
    private void pt(){};

    @Before("pt()")
    public void doBefore(){
        log.info("执行before方法");
    };

    @After("pt()")
    public void doAfter(){
        log.info("执行after方法");
    };

//    @AfterReturning("pt()")
//    public void AfterReturning(){
//        log.info("执行afterReturn 方法");
//    };
//
//    @AfterThrowing("execution(* com.bite.book.controller.*.*(..))")
//    public void doAfterThrowing(){
//        log.info("执行afterThrowing 方法");
//    };
//
//    @Around("execution(* com.bite.book.controller.*.*(..))")
//    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
//        log.info("Around方法开始执行");
//        Object result = joinPoint.proceed();
//        log.info("Around 方法结束执行");
//        return result;
//    };

}

运行结果
在这里插入图片描述

根据上述程序的运行结果 可以看出
存在多个切面类时 默认按照切面类的类名字母来排序

  • @Before 通知 字母排名靠前的先执行
  • @After通知 字母排名靠后的后执行

但这种?式不?便管理, 我们的类名更多还是具备?定含义的.
Spring 给我们提供了?个新的注解, 来控制这些切?通知的执?顺序: @Order

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

切点表达式

上?的代码中, 我们?直在使?切点表达式来描述切点. 下?我们来介绍?下切点表达式的语法.

切点表达式常?有两种表达?式

  1. execution(RR):根据?法的签名来匹配
  2. @annotation(RR) :根据注解匹配

execution表达式

execution() 是最常?的切点表达式, ?来匹配?法,语法为:

execution(<访问修饰符> <返回类型> <包名.类名.?法(?法参数)> <异常>)

其中访问修饰符和异常可以省略
在这里插入图片描述
切点表达式?持通配符表达:

    • :匹配任意字符,只匹配?个元素(返回类型, 包, 类名, ?法或者?法参数)
      a. 包名使? * 表?任意包(?层包使??个*)
      b. 类名使? * 表?任意类
      c. 返回值使? * 表?任意返回值类型
      d. ?法名使? * 表?任意?法
      e. 参数使? * 表??个任意类型的参数
  1. … :匹配多个连续的任意符号, 可以通配任意层级的包, 或任意类型, 任意个数的参数
    a. 使? … 配置包名,标识此包以及此包下的所有?包
    b. 可以使? … 配置参数,任意个任意类型的参数

@annotation

execution表达式更适?有规则的, 如果我们要匹配多个?规则的?法呢, ?如:TestController中的t1()和UserController中的u1()这两个?法.这个时候我们使?execution这种切点表达式来描述就不是很?便了.

我们可以借助?定义注解的?式以及另?种切点表达式 @annotation 来描述这?类的切点

实现步骤

  1. 编写自定义注解
  2. 使用@annotation表达式来描述切点
  3. 在连接点的方法上添加自定义注解
自定义注解

创建注解类

package com.bite.book.aspect;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}

Target 标识了 Annotation 所修饰的对象范围, 即该注解可以?在什么地?
常?取值:
ElementType.TYPE: ?于描述类、接?(包括注解类型) 或enum声明
ElementType.METHOD: 描述?法
ElementType.PARAMETER: 描述参数
ElementType.TYPE_USE: 可以标注任意类型

@Retention 指Annotation被保留的时间?短, 标明注解的?命周期
@Retention 的取值有三种:

  1. RetentionPolicy.SOURCE:表?注解仅存在于源代码中, 编译成字节码后会被丢弃. 这意味着 在运?时?法获取到该注解的信息, 只能在编译时使?. ?如 @SuppressWarnings , 以及 lombok提供的注解 @Data
    , @Slf4j
  2. RetentionPolicy.CLASS:编译时注解. 表?注解存在于源代码和字节码中, 但在运?时会被丢 弃. 这意味着在编译时和字节码中可以通过反射获取到该注解的信息, 但在实际运?时?法获 取. 通常?于?些框架和?具的注解.
  3. RetentionPolicy.RUNTIME:运?时注解. 表?注解存在于源代码, 字节码和运?时中. 这意味 着在编译时, 字节码中和实际运?时都可以通过反射获取到该注解的信息. 通常?于?些需要 在运?时处理的注解, 如Spring的 @Controller
    @ResponseBody
切面类

使? @annotation 切点表达式定义切点, 只对 @MyAspect ?效

package com.bite.book.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

//@Component
@Slf4j
@Aspect
public class MyAspectDemo {
    //前置通知
    @Before("@annotation(com.bite.book.aspect.MyAspect)")
    public void before(){
        log.info("MyAspect before");
    }

    //后置通知
    @After("@annotation(com.bite.book.aspect.MyAspect)")
    public void after(){
        log.info("MyAspect after");
    }
}

测试代码

package com.bite.book.controller;

import com.bite.book.aspect.MyAspect;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/aspectTest")

@RestController
public class AspectController {
    @MyAspect
    @RequestMapping("/t1")
    public String t1() {
        return "t1";
    }
    @MyAspect
    @RequestMapping("/t2")
    public boolean t2() {
        int a = 10 / 0;
        return true;
    }
}

在这里插入图片描述

AOP原理

上?我们主要学习了Spring AOP的应?, 接下来我们来学习Spring AOP的原理, 也就是Spring 是如何实
现AOP的.Spring AOP 是基于动态代理来实现AOP的

代理模式

代理模式, 也叫委托模式.

定义:为其他对象提供?种代理以控制对这个对象的访问. 它的作?就是通过提供?个代理类, 让我们在调??标?法的时候, 不再是直接对?标?法进?调?, ?是通过代理类间接调?.

在某些情况下, ?个对象不适合或者不能直接引?另?个对象, ?代理对象可以在客?端和?标对象之间起到中介的作?

生活中的代理

? 艺?经纪?: ?告商找艺?拍?告, 需要经过经纪?,由经纪?来和艺?进?沟通.
? 房屋中介: 房屋进?租赁时, 卖?会把房屋授权给中介, 由中介来代理看房, 房屋咨询等服务.
? 经销商: ?商不直接对外销售产品, 由经销商负责代理销售.
? 秘书/助理: 合作伙伴找?板谈合作, 需要先经过秘书/助理预约.

代理模式的主要角色

  1. Subject: 业务接?类. 可以是抽象类或者接?(不?定有)
  2. RealSubject: 业务实现类. 具体的业务执?, 也就是被代理对象.
  3. Proxy: 代理类. RealSubject的代理.

在房屋租赁这给场景下
Subject 就是提前定义了房东做的事情, 交给中介代理, 也是中介要做的事情
RealSubject: 房东
Proxy: 中介

代理模式可以在不修改被代理对象的基础上, 通过扩展代理类, 进??些功能的附加与增强.根据代理的创建时期, 代理模式分为静态代理和动态代理

? 静态代理: 由程序员创建代理类或特定?具?动?成源代码再对其编译, 在程序运?前代理类的.class ?件就已经存在了.
? 动态代理: 在程序运?时, 运?反射机制动态创建?成

静态代理

静态代理: 在程序运?前, 代理类的 .class?件就已经存在了. (在出租房?之前, 中介已经做好了相关的?作, 就等租?来租房?了)

以房屋租赁为例

  1. 定义接口
package com.example.springaop.proxy;

public interface HouseSubject {
    void rent();
}

  1. 实现接口(房东出租房子)
package com.example.springaop.proxy;

public class RealHouseSubject implements HouseSubject{
    @Override
    public void rent() {
        System.out.println("我是房东,我出租房子");
    }
}

  1. 代理(中介 帮助房东进行出租)
package com.example.springaop.proxy;

public class HouseProxy implements HouseSubject{

    public HouseProxy(HouseSubject houseSubject) {
        this.houseSubject = houseSubject;
    }

    private HouseSubject houseSubject;
    @Override
    public void rent() {
        //开始代理
        System.out.println("我是中介,开始代理");
        //代理房东出租房子
        houseSubject.rent();
        //代理结束
        System.out.println("我是中介,代理结束");
    }
}

  1. 使用
package com.example.springaop.proxy;

public class ProxyTest {
    public static void main(String[] args) {
        HouseSubject subject = new RealHouseSubject();
        //创建代理类
        HouseSubject proxy = new HouseProxy(subject);

        proxy.rent();
      
    }
}

运行结果
在这里插入图片描述
上面的例子是通过静态代理实现了对租房子这一功能进行了代理

从上述程序可以看出, 虽然静态代理也完成了对?标对象的代理, 但是由于代码都写死了, 对?标对象的每个?法的增强都是?动完成的,?常不灵活. 所以?常开发?乎看不到静态代理的场景

假设此时:中介?新增了其他业务: 代理房屋出售

此时我们就要对代码进行修改

  1. 接口定义修改
package com.example.springaop.proxy;

public interface HouseSubject {
    void rent();
    void sale();
}

  1. 房东代码修改
package com.example.springaop.proxy;

public class RealHouseSubject implements HouseSubject{
    @Override
    public void rent() {
        System.out.println("我是房东,我出租房子");
    }

    @Override
    public void sale() {
        System.out.println("我是房东,我卖房子");
    }
}

  1. 中介代码修改
package com.example.springaop.proxy;

public class HouseProxy implements HouseSubject{

    public HouseProxy(HouseSubject houseSubject) {
        this.houseSubject = houseSubject;
    }

    private HouseSubject houseSubject;
    @Override
    public void rent() {
        //开始代理
        System.out.println("我是中介,开始代理");
        //代理房东出租房子
        houseSubject.rent();
        //代理结束
        System.out.println("我是中介,代理结束");
    }

    @Override
    public void sale() {
        //开始代理
        System.out.println("我是中介,开始代理");
        //代理房东出租房子
        houseSubject.sale();
        //代理结束
        System.out.println("我是中介,代理结束");
    }
}

从上述代码可以看出, 我们修改接?(Subject)和业务实现类(RealSubject)时, 还需要修改代理类(Proxy).
同样的, 如果有新增接?(Subject)和业务实现类(RealSubject), 也需要对每?个业务实现类新增代理类(Proxy).

既然代理的流程是?样的, 有没有?种办法, 让他们通过?个代理类来实现呢?
这就需要?到动态代理技术了

动态代理

相?于静态代理来说,动态代理更加灵活.

我们不需要针对每个?标对象都单独创建?个代理对象, ?是把这个创建代理对象的?作推迟到程序运?时由JVM来实现. 也就是说动态代理在程序运?时, 根据需要动态创建?成.

Java也对动态代理进?了实现, 并给我们提供了?些API, 常?的实现?式有两种:

  1. JDK动态代理
  2. CGLIB动态代理
JDK动态代理
  1. 定义?个接?及其实现类(静态代理中的 HouseSubject 和 RealHouseSubject )
  2. ?定义 InvocationHandler 并重写 invoke ?法,在 invoke ?法中我们会调??标?法(被代理类的?法)并?定义?些处理逻辑
  3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) ?法创建代理对象

定义接口及其实现类

package com.example.springaop.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class JDKInvocationHandler implements InvocationHandler {
    //目标对象就是被代理对象
    private  Object target;
    public JDKInvocationHandler(Object target){
        this.target = target;
    }

    /**
     * 参数说明
     * proxy:代理对象
     * method:代理对象需要实现的方法,即其中需要重写的方法
     * args:method所对应方法的参数
     */
    @Override0
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //代理增强内容
        System.out.println("我是中介 开始代理");
        Object retVal = method.invoke(target,args);
        //代理增强内容
        System.out.println("我是中介,代理结束");
        return retVal;
    }
}

创建一个代理对象并使用

package com.example.springaop.proxy;

import net.sf.cglib.proxy.Enhancer;

import java.lang.reflect.Proxy;

public class Main {

    public static void main(String[] args) {
        HouseSubject houseSubject = new RealHouseSubject();
        //JDK实现动态代理
        //创建一个代理类
        //通过被代理类,被代理类实现的接口,方法调用处理器来创建
        HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
                houseSubject.getClass().getClassLoader(),
                new Class[]{HouseSubject.class},
                new JDKInvocationHandler(houseSubject)
        );
        proxy.rent();

    }
}

InvocationHandler

InvocationHandler 接?是Java动态代理的关键接?之?, 它定义了?个单??法 invoke() , ?于处理被代理对象的?法调?.

通过实现 InvocationHandler 接?, 可以对被代理对象的?法进?功能增强.

Proxy

Proxy 类中使?频率最?的?法是: newProxyInstance() , 这个?法主要?来?成?个代理对象

在这里插入图片描述

这个?法?共有 3 个参数:
Loader: 类加载器, ?于加载代理对象.
interfaces :被代理类实现的?些接?(这个参数的定义, 也决定了JDK动态代理只能代理实现了接?的 ?些类)
h : 实现了InvocationHandler 接?的对象

CGLIB代理

JDK 动态代理有?个最致命的问题是其只能代理实现了接?的类.

有些场景下, 我们的业务代码是直接实现的, 并没有接?定义. 为了解决这个问题, 我们可以? CGLIB 动态代理机制来解决.

CGLIB(Code Generation Library)是?个基于ASM的字节码?成库,它允许我们在运?时对字节码进?修改和动态?成. CGLIB 通过继承?式实现代理, 很多知名的开源框架都使?到了CGLIB. 例如 Spring中的 AOP 模块中: 如果?标对象实现了接?,则默认采? JDK 动态代理, 否则采? CGLIB 动态代理.

CGLIB 动态代理类实现步骤

  1. 定义?个类(被代理类)
  2. ?定义 MethodInterceptor 并重写 intercept ?法, intercept ?于增强?标?法,和 JDK 动态代理中的 invoke ?法类似
  3. 通过 Enhancer 类的 create()创建代理类

添加依赖
和JDK 动态代理不同, CGLIB(Code Generation Library) 实际是属于?个开源项?,如果你要使?它的话,需要?动添加相关依赖

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

?定义 MethodInterceptor(?法拦截器)
实现MethodInterceptor接?

package com.example.springaop.proxy;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.invoke.MethodHandleInfo;
import java.lang.reflect.Method;

public class CGLibIntercepter implements MethodInterceptor {
    //代理对象
    private Object target;

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

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //代理增强内容
        System.out.println("我是中介 开始代理");
        //通过反射调用被代理类的方法
        Object retVal = methodProxy.invoke(target,objects);
        //代理增强内容
        System.out.println("我是中介 代理结束");
        return retVal;
    }
}

创建代理类并使用

package com.example.springaop.proxy;

import net.sf.cglib.proxy.Enhancer;

import java.lang.reflect.Proxy;

public class Main {

    public static void main(String[] args) {
        HouseSubject target = new RealHouseSubject();
        HouseSubject proxy2 = (HouseSubject) Enhancer.create(target.getClass(),new CGLibIntercepter(target));
        proxy2.rent();
    }
}

MethodIntercepter

MethodInterceptor 和 JDK动态代理中的 InvocationHandler 类似, 它只定义了?个?法 intercept() , ?于增强?标?法.

public interface MethodInterceptor extends Callback {
	 /**
	 * 参数说明:
	 * o: 被代理的对象
	 * method: ?标?法(被拦截的?法, 也就是需要增强的?法)
	 * objects: ?法?参
	 * methodProxy: ?于调?原始?法
	 */
	 Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable;
}
Enhancer.create()

Enhancer.create() ?来?成?个代理对象

Spring AOP

Spring AOP 主要基于两种?式实现的: JDK 及 CGLIB 的?式

代理??有?个重要的属性: proxyTargetClass, 默认值为false. 也可以通过程序设置

proxyTargetClass目标对象代理方式
false实现了接口jdk代理
false未实现接口 只有实现类cglib代理
true实现了接口cglib代理
true未实现接口 只有实现类cglib代理

可以通过 @EnableAspectJAutoProxy(proxyTargetClass = true) 来设置
注意:
Spring Boot 2.X开始, 默认使?CGLIB代理
可以通过配置项 spring.aop.proxy-target-class=false 来进?修改,设置默认为jdk代理
SpringBoot设置 @EnableAspectJAutoProxy ?效, 因为Spring Boot 默认使?
AopAutoConfiguration进?装配

文章来源:https://blog.csdn.net/m0_72670269/article/details/134916280
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。