详解Spring AOP

news2024/11/15 22:39:10

   前言👀~

上一章我们介绍了SpringBoot统一功能相关的知识点,今天来讲解Spring框架另外一个核心AOP,细!!!

什么是AOP?

什么是面向切面编程呢?

什么是面向特定方法编程呢?

什么是Spring AOP?

AOP具体实现

Spring AOP 详解

AOP概念

1.切点(Pointcut)

2.连接点(Join Point)

3.通知(Advice)

4.切面(Aspect)

AOP通知类型

切点 @PointCut

切面优先级 @Order

切点表达式

execution表达式

@annotation

Spring AOP 原理

代理模式

1.静态代理

2.动态代理

1. JDK动态代理

2. CGLIB动态代理(第三方)

Spring AOP 剖析


如果各位对文章的内容感兴趣的话,请点点小赞,关注一手不迷路,讲解的内容我会搭配我的理解用我自己的话去解释如果有什么问题的话,欢迎各位评论纠正 🤞🤞🤞

个人主页:N_0050-CSDN博客

相关专栏:java SE_N_0050的博客-CSDN博客  java数据结构_N_0050的博客-CSDN博客   软件测试_N_0050的博客-CSDN博客   MySQL_N_0050的博客-CSDN博客  java EE_N_0050的博客-CSDN博客


什么是AOP?

Aspect Oriented Programming(面向切面编程),OOP是面向对象编程(开发的时候把一个个都抽象成对象来开发),两者的维度不同,处理的事情不同

什么是面向切面编程呢?

切⾯就是指某⼀类特定问题, 所以AOP也可以理解为⾯向特定⽅法编程。面向切面编程就是针对某一类特定问题统一进行编程

例子:比如people类有自己的属性和行为,但是有小一部分人生病要去医院看病,看病这个业务逻辑就不属于哪一个类,因为people泛指所有人,所有人不会都看病。AOP就是把医院看病这一个业务逻辑功能抽取出来,然后动态把这个功能切入到需要的方法(或行为)中,需要的才切入,这样便于减少系统的重复代码,降低模块间的耦合度


什么是面向特定方法编程呢?

⽐如上个章节的"登录校验",就是⼀类特定问题。前面提到的登录校验拦截器就是对"登录校验"这类问题统一的处理。所以,拦截器也是AOP的⼀种应用AOP是⼀种思想,拦截器是AOP思想的⼀种实现。Spring框架实现了这种思想,提供了拦截器技术的相关接口。简单来说: AOP是⼀种思想,就是对某⼀类事情的集中处理,或说统一处理

小结:AOP和IoC一样也是一种思想。都有具体的实现,例如IoC的具体实现就是存和取也就是五大注解和方法注解以及依赖注入。例如AOP的具体实现前面讲的拦截器和统一数据返回格式以及统一异常处理就是AOP的具体实现


什么是Spring AOP?

AOP是⼀种思想,它的实现⽅法有很多,有Spring AOP,也有AspectJ、CGLIB等。Spring AOP是其中的⼀种实现⽅式。Spring对AOP进行了实现,并且提供了一些API就是Spring AOP

前面学会了统⼀功能之后,是不是就学会了Spring AOP呢, 当然不是。拦截器作⽤的维度是URL。@ControllerAdvice 应⽤场景主要是全局异常处理,数据绑定, 数据预处理。前面的学的拦截器和数据返回格式和统一异常是Spring针对常见的场景提供统一的功能,对于一些更定制化的信息,Spring也给我们提供了一些API让我们自己写AOP作用的维度更加细致(可以根据包、类、⽅法名、参数等进⾏拦截),能够实现更加复杂的业务逻辑

引出问题:

现在有⼀些业务的执行效率⽐较低,耗时较长,我们需要对接口进行优化。第⼀步就需要定位出执行耗时⽐较长的业务⽅法,再针对该业务⽅法来进行优化

如何定位呢? 我们就需要统计当前项⽬中每⼀个业务方法的执行耗时。就是统一每一个方法的执行时间是多少

如何统计呢? 可以在业务方法运行前和运行后,记录下方法的开始时间和结束时间两者之差就是这个方法的耗时。就是在记录执行方法前和后的时间,两者一减就是这个方法执行的时间。但是呢如果接口有很多我们每一个都要这样去统计那太麻烦了。所以我们需要利用AOP的思想去解决

AOP具体实现

AOP就可以做到在不改动这些原始方法的基础上,针对特定的方法进行功能的增强。AOP的作用:在程序运⾏期间在不修改源代码的基础上对已有方法进行增强(无侵⼊性 解耦

利用AOP的思想解决上述的问题,代码如下,这里面的一些注解下面会进行解释。首先引入依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
@Slf4j
//标记为切面类
@Aspect
@Component
public class TimeAspect {

    //表示AOP在 哪个环节起作用 在哪个方法起作用
    @Around("execution(* com.example.bookdemo.controller.*.*(..))")
    public Object timeCost(ProceedingJoinPoint joinPoint) throws Throwable {
        //目标方法执行前的逻辑
        long start = System.currentTimeMillis();
        log.info("方法执行前");
        //joinPoint表示目标方法 调用这个方法就是执行目标方法
        Object result = joinPoint.proceed();
        //目标方法执行后的逻辑
        long end = System.currentTimeMillis();
        log.info("方法执行后");
        log.info(joinPoint + "消耗时间" + (end - start) + "ms");
        return result;
    }
}

输出结果,注意这里可以对所以的接口都进行测试,这里就简单对两个接口进行测试

@Aspect:这个注解标识这是⼀个切⾯类

@Around:这个注解是环绕通知,在⽬标⽅法的前后都会被执行。后⾯的表达式表示对哪些⽅法进行增强。里面填写的内容就是AOP在哪个环节起作用对哪些方法起作用

ProceedingJoinPoint:目标方法

通过上⾯的程序,我们也可以感受到AOP面向切面编程的⼀些优势:

    •    1.代码无侵入:不修改原始的业务⽅法, 就可以对原始的业务⽅法进⾏了功能的增强或者是功能的改变


    •    2.减少了重复代码


    •    3.提⾼开发效率


    •    4.维护⽅便


Spring AOP 详解

AOP概念

1.切点(Pointcut)

切点的作用就是提供⼀组规则,告诉程序对哪些方法来进行功能增强切点就是⼀组规则通过表达式来描述。就是上面@Around注解后面的表达式称为切点,也可以叫切点表达式。用于定义在哪些连接点执行通知


2.连接点(Join Point)

满足切点表达式规则的方法,就是连接点,也就是可以被AOP控制的⽅法。可以简单理解目标方法就是连接点,表达式中的controller.*.*,第一个*表示这个包下的所有controller,第二个*就是里面的所有方法,第二个*就可以看作是连接点

切点和连接点的关系:连接点是满足切点表达式的元素切点可以看做是保存了众多连接点的⼀个集合。连接点是由切点来描述的,这样理解就是这些方法都在这个切点里面,前面说了切点可以看做是保存了众多连接点的⼀个集合

3.通知(Advice)

就是具体要做的⼯作,就是具体的逻辑,也就是共性功能。比如上面的timeCost方法中的代码就统一对controller中所有的方法统计执行时间,在AOP中我们把重复的代码逻辑抽取出来单独定义,这部分代码就是通知的内容

4.切面(Aspect)

切⾯(Aspect) = 切点(Pointcut) + 通知(Advice),可以把一个切面类中的一个个包含通知和切点的方法看作是一个个切面大白话就好比老师通知班级里的所有学生下午要考试,连接点就是所有学生,下午考试就是一个通知,老师通知就是切点,三者加起来就是一个切面。切面所在的类, 我们⼀般称为切面类


AOP通知类型

@Around:环绕通知,此注解标注的通知方法在⽬标方法前, 后都被执行。就是可以在目标方法执行前后添加一些逻辑也是会被执行的,是在这个切面,后面说的都是切面

注意:

    •    @Around环绕通知需要调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑⽬标⽅法执⾏

    •    @Around必须要返回结果的环绕通知方法的返回值,必须指定为Object,来接收原始⽅法的返回值,否则原始⽅法执行完毕,是获取不到返回值的

@Before:前置通知,此注解标注的通知方法在⽬标方法前被执行,就是在目标方法执行前添加一些逻辑会被执行


@After:后置通知,此注解标注的通知方法在⽬标方法后被执行,无论是否有异常都会执行


@AfterReturning:返回后通知,此注解标注的通知方法在⽬标方法后被执行,有异常不会执行


@AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行


下面进行演示,先演示接口响应正常,观察一下这些通知的执行顺序

@Slf4j
@Aspect
@Component
public class AspectDemo {

    @Before("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
    public void doBefore() {
        log.info("执行AspectDemo Before...");
    }

    @After("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
    public void doAfter() {
        log.info("执行AspectDemo After...");
    }

    @AfterReturning("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
    public void doAfterReturning() {
        log.info("执行AspectDemo AfterReturning...");
    }

    @AfterThrowing("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
    public void doAfterThrowing() {
        log.info("执行AspectDemo AfterThrowing...");
    }

    @Around("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("执行AspectDemo Around之前...");
        Object result = joinPoint.proceed();
        log.info("执行AspectDemo Around之后...");
        return result;
    }

}

输出结果,先执行Around再执行before,再执行AfterReturning,先执行After再执行Around


接着演示接口响应异常

@Slf4j
@Aspect
@Component
public class AspectDemo {

    @Before("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
    public void doBefore() {
        log.info("执行AspectDemo Before...");
    }

    @After("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
    public void doAfter() {
        log.info("执行AspectDemo After...");
    }

    @AfterReturning("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
    public void doAfterReturning() {
        log.info("执行AspectDemo AfterReturning...");
    }

    @AfterThrowing("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
    public void doAfterThrowing() {
        log.info("执行AspectDemo AfterThrowing...");
    }

    @Around("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("执行AspectDemo Around之前...");
        Object result = joinPoint.proceed();
        log.info("执行AspectDemo Around之后...");
        return result;
    }

}

输出结果,当发生异常,AfterReturning不执行,Around后也不执行。因为AfterReturning是返回后通知,都来不及返回所以后面的Around后也不执行


切点 @PointCut

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

代码如下

@Slf4j
@Aspect
@Component
public class AspectDemo {

    @Pointcut("execution(* com.example.aopdemo2.demos.controller.*.*(..))")
    private void pt() {

    }

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

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

    @AfterReturning("pt()")
    public void doAfterReturning() {
        log.info("执行AspectDemo AfterReturning...");
    }

    @AfterThrowing("pt()")
    public void doAfterThrowing() {
        log.info("执行AspectDemo AfterThrowing...");
    }

    @Around("pt()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("执行AspectDemo Around之前...");
        Object result = joinPoint.proceed();
        log.info("执行AspectDemo Around之后...");
        return result;
    }

}

注意:如果其他切⾯类需要使用这个切点,把切点的声明改为public即可,使用时,类的全限定名称+切点名称,全限定名称就是包名+类名


切面优先级 @Order

⼀个项⽬中,定义了多个切⾯类时, 并且这些切面类的多个切⼊点都匹配到了同⼀个目标方法。当目标方法运行的时候, 这些切⾯类中的通知方法都会执行,那么这⼏个通知方法的执行顺序是什么样的呢?结果已经在上面进行演示了

如下图输出的结果,可以发现优先级高的,最先执行before,最后执行after

使用@Order注解来定义切面的优先级数字越小,优先级越高。先执行优先级较⾼的切⾯, 再执行优先级较低的切⾯, 最终执行⽬标⽅法。下面进行演示,创建三个类搭配@Order注解

@Slf4j
@Aspect
@Component
@Order(3)
public class AspectDemo1 {

    @Before("com.example.aopdemo2.demos.aspect.AspectDemo.pt()")
    public void doBefore() {
        log.info("执行AspectDemo1 Before...");
    }

    @After("com.example.aopdemo2.demos.aspect.AspectDemo.pt()")
    public void doAfter() {
        log.info("执行AspectDemo1 After...");
    }
}
@Slf4j
@Aspect
@Component
@Order(1)
public class AspectDemo2 {

    @Before("com.example.aopdemo2.demos.aspect.AspectDemo.pt()")
    public void doBefore() {
        log.info("执行AspectDemo2 Before...");
    }

    @After("com.example.aopdemo2.demos.aspect.AspectDemo.pt()")
    public void doAfter() {
        log.info("执行AspectDemo2 After...");
    }

}
@Slf4j
@Aspect
@Component
@Order(2)
public class AspectDemo3 {

    @Before("com.example.aopdemo2.demos.aspect.AspectDemo.pt()")
    public void doBefore() {
        log.info("执行AspectDemo3 Before...");
    }

    @After("com.example.aopdemo2.demos.aspect.AspectDemo.pt()")
    public void doAfter() {
        log.info("执行AspectDemo3 After...");
    }

}

输出结果,可以发现我把AspectDemo2优先定义最高,先输出Before,after最后才输出


切点表达式

切点表达式常见有两种表达⽅式:

    •    1. execution():根据⽅法的签名来匹配

    •    2. @annotation() :根据注解匹配
 

execution表达式

execution() 是最常用的切点表达式, ⽤来匹配⽅法。其中访问修饰符和异常可以省略,并且execution表达式更适用有规则的



 前面我们一直使用的就是execution表达式

切点表达式支持通配符表达:

@annotation

execution表达式更适⽤有规则的,如果我们要匹配多个无规则的方法呢。我们可以借助⾃定义注解的方式以及另⼀种切点表达式 @annotation 来描述这⼀类的切点

步骤:

    •    1. 编写自定义注解,创建一个注解类,和创建Class⽂件⼀样的流程, 选择Annotation就可以了

    •    2. 使⽤ @annotation 表达式来描述切点

    •    3. 在连接点的⽅法上添加⾃定义注解


    •    自定义注解,就像平常创建类一样,选择Annotation即可

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

    •    下面进行演示,先在切面类中通知注解后的表达式换成@annotation,如下面的代码

@Slf4j
@Aspect
@Component
public class AspectDemo1 {

    @Before("@annotation(com.example.aopdemo2.demos.aspect.MyAspect)")
    public void doBefore() {
        log.info("执行AspectDemo1 Before...");
    }

    @After("@annotation(com.example.aopdemo2.demos.aspect.MyAspect)")
    public void doAfter() {
        log.info("执行AspectDemo1 After...");
    }
}

然后我把自定义注解加指定的方法上

@RequestMapping("/hello")
@RestController
public class HelloController {

    @MyAspect
    @RequestMapping("/h1")
    public String h1() {
        return "h1";
    }

    @RequestMapping("/h2")
    public String h2() {
        return "h2";
    }
}

接下来我对接口进行测试,看看输出结果,先对h1接口进行测试

再对h2接口进行测试

可以发现啥也没输出,因为这个接口没有加上自定义注解

    •     还可以这样写,带有@RequestMapping注解的都会执行@After注解

@Slf4j
@Aspect
@Component
public class AspectDemo1 {

    @Before("@annotation(com.example.aopdemo2.demos.aspect.MyAspect)")
    public void doBefore() {
        log.info("执行AspectDemo1 Before...");
    }

    @After("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void doAfter() {
        log.info("执行AspectDemo1 After...");
    }
}
@RequestMapping("/hello")
@RestController
public class HelloController {

    @MyAspect
    @RequestMapping("/h1")
    public String h1() {
        return "h1";
    }

    @RequestMapping("/h2")
    public String h2() {
        return "h2";
    }
}

我们观察h2接口,看看输出结果,如我们所料带有@RequestMapping注解会执行@After注解


Spring AOP的实现方式:

    •    1. 基于注解 @Aspect,结合上面的代码看


    •    2. 基于⾃定义注解,结合上面的代码看


    •    3. 基于Spring API (通过xml配置的⽅式, ⾃从SpringBoot ⼴泛使⽤之后, 这种⽅法⼏乎看不到了, 课下⾃⼰了解下即可)


    •    4. 基于代理来实现(更加久远的⼀种实现⽅式, 写法笨重, 不建议使⽤)


Spring AOP 原理

    •    Spring AOP 是基于动态代理来实现AOP的

代理模式

为其他对象提供⼀种代理以控制对这个对象的访问。它的作用就是通过提供⼀个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进⾏调用,而是通过代理类间接调用代理可以看作是调用目标的一个包装,通常用来在调用真实的目标之前进行一些逻辑处理,消除一些重复的代码

例子:就像我们现在租房子的时候大部分通过中介然后和房东沟通然后租到房子。还可以理解为广告商找艺人代言,需要经过经纪人一样,由经纪人和艺人沟通

    •    使用代理前

    •    使用代理后

代理模式的主要角色:

    •  1. Subject:业务接口类,可以是抽象类或者接口(不⼀定有)。上面例子中提前定义了房东做的事情,交给中介代理,也是中介要做的事情

    •  2. RealSubject:业务实现类,具体的业务执行,也就是被代理对象。例子中的房东

    •  3. Proxy:代理类,RealSubject的代理。例子中的中介


适配器模式和代理模式以及代理模式和装饰器的区别:

首先要明确都叫设计模式了,我们要从它们的思想和解决办法去入手,对于适配器模式它解决的问题呢是不兼容相关的问题,而代理模式呢是通过代理对象来完成目标对象所需要完成的任务并且可以在其基础上在进一步的进行扩展,也可以说是在不改变目标对象的源代码情况下对功能的进一步增强


装饰器模式:目的是在不改变原始类接口、不对原始类复杂化的情况下,对原始类进行本身功能增强,并且支持嵌套多装饰器多功能增强


代理模式:目的是在不改变原始类接口的情况下,为原始类提供代理类,对其进行访问控制,实现额外功能它和装饰器的区别在于不是增强原始类本身对应的功能


1.静态代理

静态代理指的是我们预先编码好一个代理类,在程序运行前,代理类的 .class⽂件就已经存在了。而动态代理指的是运行时生成代理类

例子:像上面租房子的时候,房东先把要交代的事先和中介说好,在用户来租房之前,中介已经提前做好相应的准备了,类似单例模式中的饿汉模式一样我们已经创建好实例,就等其他类通过调用方法来获取到这个对象的实例,注意是类似

    •    模拟实现静态代理,先搞个接口

public interface HouseSubject {
    void rentHouse();
}

接着是目标对象,也就是例子中的房东

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

接着就是代理对象,也就是例子中的中介

public class HouseProxy implements HouseSubject {
    private RealSubject realSubject;

    public HouseProxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void rentHouse() {
        System.out.println("开始代理...");
        realSubject.rentHouse();
        System.out.println("结束代理...");
    }
}

接着创建实例

public class Main {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        HouseProxy proxy = new HouseProxy(realSubject);
        proxy.rentHouse();
    }
}

输出结果


缺点:虽然静态代理也完成了对⽬标对象的代理,但是由于代码都写死了,对⽬标对象的每个⽅法的增强都是手动完成的,非常不灵活


2.动态代理

在程序运行时,运用反射机制动态创建而成。相⽐于静态代理来说,动态代理更加灵活,不需要针对每个⽬标对象都单独创建⼀个代理对象,⽽是把这个创建代理对象的⼯作推迟到程序运行时由JVM来实现。也就是说动态代理在程序运行时, 根据需要动态创建⽣成

例子:类似单例模式中的懒汉模式,等需要的时候调用方法再创建实例,注意是类似。动态代理中的代理对象可以动态生成,就像每个房东找中介代理可以不局限于这一个中介,想出租的时候随便找一个都可以


注意:动态代理底层利用了反射机制,反射包下Proxy类!!!

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

1. JDK动态代理

步骤:首先自定义一个类实现InvocationHandler接口(反射包中的),接着还要搞一个目标对象,我们在自定义类的构造方法中可以传入一个目标对象,这个目标对象就是例子中的房东,里面有要执行的方法,然后还有就是重写InvocationHandler接口的Invoke方法。invoke ⽅法中我们会调用⽬标⽅法(被代理类的⽅法)并⾃定义⼀些处理逻辑。具体看下面代码的实现

Invoke方法:其中proxy参数代表代理对象就是中介method方法就是目标对象(房东)要出租或出售房子这些事情代理对象(中介)来负责实现,args就是method中所对应⽅法的参数

    •    JDK动态代理实现

public class JDKInvocation implements InvocationHandler {
    //目标对象 被代理对象
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK开始代理...");
        //通过反射机制调用目标对象的方法
        Object result = method.invoke(target, args);
        System.out.println("JDK结束代理...");
        return result;
    }
}
public class Main {
    public static void main(String[] args) {
        //目标对象
        RealSubject target = new RealSubject();

        //创建⼀个代理类:通过被代理类、被代理类实现的接⼝、⽅法调用处理器来创建
        HouseSubject JDKProxy = (HouseSubject) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class[]{HouseSubject.class},
                new JDKInvocation(target));
        JDKProxy.rentHouse();
    }
}

输出结果


    •    这样子做,只需要在业务接口/类和目标对象里修改即可,不需要在代理对象中修改

public interface HouseSubject {
    void rentHouse();

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

    @Override
    public void saleHouse() {
        System.out.println("我是房东,我要出售房子");
    }
}

输出结果


newProxyInstance方法:这个⽅法主要用来⽣成⼀个代理对象

这个方法⼀共有 3 个参数:

Loader:类加载器,用于加载代理对象

interfaces:被代理类实现的⼀些接口(这个参数的定义,也决定了JDK动态代理只能代理实现了接口的⼀些类)

h:实现了 InvocationHandler 接口的对象


    •    newProxyInstance方法源码中里面就已经写明了参数是interfaces


注意:JDK只能代理接口不能代理类,就是代理对象不能直接调用目标对象的方法


2. CGLIB动态代理(第三方)

JDK 动态代理有⼀个最致命的问题是其只能代理实现了接口的类,CGLIB动态代理既可以代理接口又可以代理类

CGLIB 动态代理类实现步骤:

    •    1.首先添加依赖,定义⼀个类(被代理类)


    •    2. ⾃定义 MethodInterceptor 并重写 intercept ⽅法, intercept ⽤于增强⽬标⽅法,和 JDK 动态代理中的 invoke ⽅法类似


    •    3. 通过 Enhancer 类的 create()创建代理类


  •    CGLIB动态代理实现,先演示代理接口,先引入依赖

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>
public class CGLIBInterceptor implements MethodInterceptor {

    //目标对象 被代理对象
    private Object target;

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

    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("CGLIB开始代理...");
        Object result = methodProxy.invoke(target, args);
        System.out.println("CGLIB结束代理...");
        return result;
    }
}
public class Main {
    public static void main(String[] args) {
        //目标对象
        RealSubject target = new RealSubject();

        //create方法用来⽣成⼀个代理对象
        HouseSubject CGLIBProxy = (HouseSubject) Enhancer.create(target.getClass(), new CGLIBInterceptor(target));
        CGLIBProxy.rentHouse();
    }
}

输出结果

  •   演示代理类,前面代码一样,只需要把创建代理对象的时候修改一下即可

public class Main {
    public static void main(String[] args) {
        //目标对象
        RealSubject target = new RealSubject();

        RealSubject CGLIBProxy = (RealSubject) Enhancer.create(target.getClass(), new CGLIBInterceptor(target));
        CGLIBProxy.rentHouse();
    }
}

输出结果

注意:两者动态代理写法都是固定的,以及搞清楚代理的目的,是为了对目标对象进行功能的增强,类似房东出租房子不提供水电维修,但是中介提供,这就是功能增强的体现


Spring AOP 剖析

Spring对于AOP的实现,基本上都是靠 AnnotationAwareAspectJAutoProxyCreator 去完成⽣成代理对象的逻辑在⽗类 AbstractAutoProxyCreator 中

1.Spring AOP是怎么实现的?

    •    Spring AOP是基于动态代理实现的

2.动态代理是怎么实现的?

    •    Spring 动态代理主要基于 JDK 及 CGLIB 两种⽅式实现的

3.Spring使用的是哪个?

    •    JDK和CGLIB两个都用

4.什么时候用JDK,什么时候用CGLIB?

SpringBoot 2.x以后默认使用CGLIB 代理不管接口还是类在这之前默认是jdk代理,但是也要看是不是接口,是接口的话是JDK代理,类的话还是CGLIB代理。我们可以通过配置项 spring.aop.proxy-target-class=false 来进行修改,设置默认为jdk代理。如果设置为true则全都使用CGLIB代理。

    •    设置代理模式

5.JDK和CGLIB 有什么区别?

    •    最大的区别就是JDK只能代理接口,而CGLIB能代理类和接口


以上便是Spring AOP的知识点,AOP作为Spring两大核心功能之一也是很重要的另外一个IoC之前已经讲解过了,我们下一章再见💕

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2089704.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Bahdanau注意力机制

介绍 在Bahadanu注意力机制中&#xff0c;本质上是序列到序列学习的注意力机制实现&#xff0c;在编码器-解码器结构中&#xff0c;解码器的每一步解码过程都依赖着整个上下文变量&#xff0c;通过Bahdanau注意力&#xff0c;使得解码器在每一步解码时&#xff0c;对于整个上下…

ET6框架(七)Excel配置工具

文章目录 一、Excel表的基本规则&#xff1a;二、特殊特殊标记三、编译路径说明四、动态获取数据五、可导表类型查看: 一、Excel表的基本规则&#xff1a; 在框架中我们的Excel配置表在ET > Excel文件夹中 1.在表结构中需要注意的是起始点必须在第三行第三列&#xff0c;且…

91.游戏的启动与多开-游戏启动

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;易道云信息技术研究院 上一个内容&#xff1a;90.游戏安全项目-项目搭建与解析 以90.游戏安全项目-项目搭建与解析它的代码为基础进行…

[java][代码] java中date格式化输出时间字符串

Date date new Date();//具备默认的风格//DateFormat dateFormDateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG);DateFormat dateFormnew SimpleDateFormat("yyyy-mm-dd");System.out.println(dateForm.format(date)); 这段Java代码演示了如何使用S…

YOLOv9改进策略【模型轻量化】| PP-LCnet

一、本文介绍 本文记录的是利用PP-LCNet中的DepSepConv模块优化YOLOv9中的RepNCSPELAN4。YOLOv9在使用辅助分支后&#xff0c;模型的参数量和计算量相对较大&#xff0c;本文利用DepSepConv模块改善模型结构&#xff0c;使模型在几乎不增加延迟的情况下提升网络准确度。 文章目…

海外新闻稿发布对区块链项目具有深远的影响

在知名媒体平台上发布新闻稿对区块链项目具有深远的影响&#xff0c;例如全球雅虎&#xff0c;彭博社这些全球知名财经媒体&#xff0c;能够显著提升项目的曝光度和信誉&#xff0c;吸引潜在投资者以及其他利益相关者的关注。以下是几个方面的详细分析&#xff1a; 1. 增强品牌…

区块链通证系统功能分析

区块链通证系统功能分析涉及多个关键方面&#xff0c;以确保系统能够满足不同的业务需求和合规性要求。 同质与非同质通证&#xff1a;区块链通证系统需要支持同质通证&#xff08;如ERC-20&#xff09;和非同质通证&#xff08;如ERC-721&#xff09;&#xff0c;以适应不同类…

如何快速掌握销售数据?一张报表就够了

在销售管理中&#xff0c;数据是企业做出战略决策的重要依据。有效的销售数据分析不仅能帮助企业精准把握市场动向&#xff0c;还能提高销售团队的工作效率&#xff0c;优化客户关系管理。然而&#xff0c;面对海量的销售数据&#xff0c;如何高效地解读这些数据呢&#xff1f;…

Java SpringBoot实现大学生平时成绩量化管理系统:一步步教你构建高效成绩统计,集成MySQL数据库,打造自动化评分流程

&#x1f34a;作者&#xff1a;计算机毕设匠心工作室 &#x1f34a;简介&#xff1a;毕业后就一直专业从事计算机软件程序开发&#xff0c;至今也有8年工作经验。擅长Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等。 擅长&#xff1a;按照需求定制化开发项目…

three.js 编辑器,动画,着色器, cesium 热力图,聚合点位,大量点线面, 图层,主题,文字,等众多案例中心

对于大多数的开发者来言&#xff0c;看了很多文档可能遇见不到什么有用的&#xff0c;就算有用从文档上看&#xff0c;把代码复制到自己的本地大多数也是不能用的&#xff0c;非常浪费时间和学习成本&#xff0c; 尤其是three.js &#xff0c; cesium.js 这种难度较高&#xff…

ThinkPHP之入门讲解

文章目录 1 ThinkPHP1.1 框架1.1.1 目录讲解1.1.1.1 5.x1.1.1.2 6.0以上 1.1.2 配置文件1.1.2.1 5.x1.1.2.2 6.0以上 1.1.3 函数文件1.1.3.1 5.x1.1.3.1 6.0以上 1.2 控制器1.2.1 控制器的后缀1.2.2 框架中的命名空间1.2.3 url访问1.2.4 调试模式1.2.4.1 5.x1.2.4.2 6.0以上 1.…

Oracle迁移至openGauss的工具:ora2op的安装配置

目录 前言 1. ora2op的下载 1.1 下载地址 1.2 ora2op 介绍 2. ora2op的安装 2.1 安装perl的依赖包 2.2 安装连接Oracle数据库的模块 2.3 安装ora2op 2.4 安装连接openGauss数据库的模块 前言 本工具是使用perl&#xff0c;在安装时会遇到各种问题&#xff0c;解决方式…

Keil5 Debug模式Watch窗口添加的监控变量被自动清除

Keil5 Debug模式Watch窗口添加的监控变量被自动清除 问题解决记录 问题描述&#xff1a;每次进入Debug模式时&#xff0c;watch窗口里面上一次调试添加的监控变量都会被全部清掉 如图&#xff1a; 退出Debug模式后&#xff0c;重新进入Debug模式&#xff1a; 解决方法&…

用户体验设计案例:提升电商网站的用户体验

在数字化时代&#xff0c;用户体验设计&#xff08;UX Design&#xff09;已成为影响品牌成功的关键因素之一。尤其是在竞争激烈的电商行业&#xff0c;如何通过优质的用户体验来吸引和留住客户&#xff0c;是每个企业都需要面对的挑战。本文将通过一个具体的电商网站设计案例&…

解析 uni-app 小程序分包策略:合理优化包体积分布

引言 微信小程序的流行使得越来越多的开发者投入到小程序的开发中。但是&#xff0c;随着项目规模的增大&#xff0c;小程序的性能也会面临一些挑战。其中&#xff0c;小程序分包策略是提升性能的重要一环。 同时&#xff0c;uni-app 的流行也使众多的移动端开发者选择使用 u…

AcWing895. 最长上升子序列

这个代码不知道怎么说&#xff0c;反正就是对着代码手算一次就懂了&#xff0c;无需多言&#xff0c;就是俩for循环里面的第二层for的循环条件是j<i,j是从下标1往下标i-1遍历的&#xff0c;每次a【j】<a【i】就在答案数组f【i】上面做出更新。基本的输入样例已经可以覆盖…

揭秘数字水印技术:使用PyQt5实现图像中的LSB隐写术

在当今的数字化世界中&#xff0c;保护信息的安全性和隐秘性变得尤为重要。无论是在保护版权的数字水印&#xff0c;还是在隐秘传输信息的过程中&#xff0c;数字隐写术&#xff08;Steganography&#xff09;都是一种不可或缺的技术。今天&#xff0c;我们将带领大家探索一种简…

关于LLC知识14

1、LLC必须工作在感性区 2、为了降低LLC进入容性区后MOS管的电流应力&#xff0c;必须要选择快管&#xff0c;对体二极管的反向恢复参数有要求&#xff1a;trr<200ns 3、对于上下管的死区时间不能太短&#xff0c;否则电容无法充放电完成&#xff0c;就无法实现ZVS导通 如…

DNN学习平台(GoogleNet、SSD、FastRCNN、Yolov3)

DNN学习平台&#xff08;GoogleNet、SSD、FastRCNN、Yolov3&#xff09; 前言相关介绍1&#xff0c;登录界面&#xff1a;2&#xff0c;主界面&#xff1a;3&#xff0c;部分功能演示如下&#xff08;1&#xff09;识别网络图片&#xff08;2&#xff09;GoogleNet分类&#xf…

短视频去哪里找素材?16个有短视频素材APP网站分享给你

短视频创作爱好者们&#xff0c;你们好&#xff01;在这个充斥视觉内容的年代&#xff0c;制作一部引人注目的短视频无疑是一项挑战&#xff0c;但也是一种艺术。要想打造出色的视频内容&#xff0c;首先需要的是高质量的素材。今天&#xff0c;我将向大家推荐几个非常棒的短视…