【Spring】Spring AOP原理

目录

前言

代理模式

静态代理

优缺点

动态代理

JDK动态代理

工作原理

JDK动态原理实现关键步骤

CGLib动态代理 

 CGLIB动态代理实现关键步骤

总结


前言

在上一篇中,我们讲解了什么是AOP,以及Spring AOP是如何使用的,那么本篇我们就来学习一下Spring AOP的原理,也就是Spring是如何实现AOP的。

Spring AOP是基于动态代理来实现AOP的,什么是动态代理呢?动态代理其实是一种代理模式。

代理模式

代理模式,又可以称为委托模式,为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

简单来说:代理模式可以在不改变原对象的基础上,通过引入一个代理对象来间接访问原对象,从而实现堆原对象的某些操作的控制

使用代理前:

使用代理后:

 在生活中,能够体现代理模式的有很多,如房屋中介。当我们想要租房的时候,如果我们直接去找房东,如果房东一个个带我们去看,就会很浪费时间,若房东比较忙,就有可能会处理不过来,所以房东就会把租房这件事情交给中介,而我们直接去找中介,并提出我们的需求,中介就会帮助我们找到合适的房子。这样就极大的方便了我们租房的流程。

代理模式中主要角色:

  • Subject:业务接口类,可以是抽象类或者接口(不一定有);
  • RealSubject:业务实现类,具体的业务逻辑,也就是被打理对象,如房东;
  • Proxy:代理类。RealSubject 的代理,通过中介体现。

代理模式我们可以分为两种:静态代理动态代理

在静态代理中,我们对目标对象中的每个方法的增强都是手动完成的,耦合性高,而动态代理,则是通过在运行时创建一个子类实例来实现的,可以更加灵活地实现代理。

  • 静态代理:在编译时就已经确定的代理类,代理类和真实主题类通常实现相同的接口。
  • 动态代理:在运行时利用反射机制动态生成代理类,不需要先定义代理类,而是在运行时根据指定的类和接口动态生成代理类。

静态代理

静态代理是指代理类在程序运行前就已经定义好,与目标类的关系在运行前就确定

在静态代理中,真实主题类和代理类都实现一个相同的接口,代理类通过调用真实主题类的方法来实现接口中定义的业务逻辑,同时可以在调用前后增加额外的操作。

优缺点

  • 优点:简单直观,易于理解和实现;
  • 缺点:
    • 每个目标类都需要创建对应的代理类,如果目标类太多,就会导致类的数量急剧增加,增加了系统的复杂性;
    • 代理类是硬编码,不灵活。

我们以前面讲的中介为例:

在用户租房前,中介和房东需要先协商好,中介就能帮助房东出租或出售房子,当有人去找中介讲需求的时候,中介就可以直接联系客户和房东进行对接。

通过代码展示:

定义一个接口(房东想要的事,也就是中介要做的事):

package com.example.demo.aspect;

public interface HouseSubject {
    void rent();
}

房东:

package com.example.demo.aspect;

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

中介:

package com.example.demo.aspect;

public class ProxyHouse implements HouseSubject{
    private RealHouseSubject realHouseSubject;
    public ProxyHouse(RealHouseSubject realHouseSubject){
        this.realHouseSubject = realHouseSubject;
    }
    @Override
    public void rent() {
        System.out.println("我是中介,开始代理");
        realHouseSubject.rent();
        System.out.println("我是中介,结束代理");

    }
}

客户租房:

通过这个案例,我们能够进一步理解静态代理。

那么如果房东现在想要对房子进行出租,那么中介和房东就需要再一次协商。

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

中介:

package com.example.demo.aspect;

public class ProxyHouse implements HouseSubject{
    private RealHouseSubject realHouseSubject;
    public ProxyHouse(RealHouseSubject realHouseSubject){
        this.realHouseSubject = realHouseSubject;
    }
    @Override
    public void rent() {
        System.out.println("我是中介,开始代理");
        realHouseSubject.rent();
        System.out.println("我是中介,结束代理");

    }

    @Override
    public void sale() {
        System.out.println("我是中介,开始代理");
        realHouseSubject.sale();
        System.out.println("我是中介,结束代理");
        
    }
}

房东:

package com.example.demo.aspect;

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

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

客户买房:

package com.example.demo.aspect;

public class Main {
    public static void main(String[] args) {
        RealHouseSubject realHouseSubject = new RealHouseSubject();
        ProxyHouse proxyHouse = new ProxyHouse(realHouseSubject);
//        proxyHouse.rent();
        proxyHouse.sale();
    }
}

我们可以看到,当我们想要对业务进行修改的时候,那么对应的其它两个实现类进行修改,若增加的业务越多,修改的就越多。

那么有没有一种方法?让他们通过一个代理类来实现呢?这就需要用到动态代理了。

动态代理

动态代理是在运行时利用反射机制动态生成代理类的代理方式,不需要事先定义代理类,而是在运行时根据指定的类和接口动态生成代理类。

动态代理不需要修改原始对象,就能对对象的功能进行增强,例如实现横切关注点,日志记录、性能监控、事务管理等。

常见的动态代理有 JDK代理 CGLib代理

JDK动态代理

JDK动态代理是Java标准库中提供的一种动态代理机制,允许在运行时动态生成实现了一个或多个接口的代理类,而无需事先知道具体的类

工作原理
  • 使用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口实现。
  • 目标类必须实现一个或多个接口
  • 代理类在运行时生成,实现了与目标类相同的接口。
  • 通过实现 InvocationHandler 接口,可以自定义方法调用的行为,即在调用目标方法前后添加额外逻辑

不过JDK动态代理其实有个缺点:只能代理实现了接口的类,对于没有实现接口的类,是不能代理实现的

JDK动态原理实现关键步骤
  • 定义接口:首先需要定义一个接口,该接口将由目标对象和代理对象实现。
  • 创建目标对象:需要先创建目标对象的实例,该实例将被代理对象代理。
  • 实现 InvocationHandler 接口:创建一个实现  java.lang.reflect.InvocationHandler 接口的类,该类将定义代理对象的方调用逻辑。在实现该接口的类中,需要重写 invoke() 方法,以便在目标对象的方法调用前后执行额外的业务逻辑。
  • 创建代理对象:使用 java.lang.reflect.Proxy 类 和 InvocationHandler 对象创建代理对象。具体来说就是要调用 Proxy类中的静态方法newProxyInstance() 创建一个代理对象,该实例将调用InvocationHandler对象的invoke() 方法来处理方法调用
  • 使用代理对象:使用代理对象调用目标对象的方法时,会在方法执行前后执行额外的逻辑。

我们利用中介的例子来用代码实现:

实现接口

package com.example.demo.aspect;

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

创建目标对象

package com.example.demo.aspect;

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

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

 创建一个实现InvocationHandler接口的代理类:

package com.example.demo.aspect;

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;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //目标对象执行之前,代理增强
        System.out.println("我是中介,开始代理");
        //通过反射执行被代理对象的方法
        Object result=method.invoke(target, args);
        //目标对象执行完成之后,代理增强
        System.out.println("我是中介,结束代理");
        return result;
    }
}

创建代理对象并调用

package com.example.demo.aspect;
import java.lang.reflect.Proxy;
public class DynamicMain {
    public static void main(String[] args) {
        
        HouseSubject target = new RealHouseSubject();
        HouseSubject proxy= (HouseSubject) Proxy.newProxyInstance(
                target.getClass().getClassLoader(), 
                new Class[]{HouseSubject.class},
                new JDKInvocationHandler(target));
                proxy.rent();
    }
}

结果

那么上面这些InvocationHandler接口以及Proxy到底是什么呢?

InvocationHandler接口是java动态代理的关键接口之一,定义了一个单一方法 invoke() 方法,该方法是代理示例调用的中心分发点。当代理实例的任意方法被调用时,调用会自动委托给关联的 InvocationHandler 实例的 invoke() 方法。

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
  • proxy:就是要代理对象本身,可以用来调用目标对象的其他方法或者属性;
  • method被调用的方法的 Mathod 对象,提供了关于方法的详细信息,如方法名、参数类型等;
  • args调用方法时传递的参数数组

Proxy类是位于 java.lang.reflect 包中的一个类,提供了创建动态代理类和实例的静态方法。它是JDK动态代理机制的入口点。Proxy类没有公共的构造函数,所以不能直接实例化。但我们可以用它里面的 newProxyInstance 来创建代理实例。

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler invocationHandler)
        throws IllegalArgumentException
  • loader定义了代理类的类加载器,通常是目标类的类加载器;
  • interfaces代理类需要实现的接口数组,代理类中会实现这些接口中定义的所有方法;
  • invocationHandler:一个InvocationHandler示例,定义了当代理实例的方法被调用时的处理逻辑

CGLib动态代理 

JDK动态代理有一个致命的问题就是只能代理实现了接口的类。

但是在有些情况下,我们的业务代码是直接实现的,并没有接口定义,那就不能实现JDK代理了,为了解决这个问题,我们可以用CGLIB动态代理机制来解决。

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,通过字节码技术在运行时为没有实现接口的目标类生成子类作为代理类

CGLIB能代理任何未实现接口的类,是因为CGLIB是通过继承方式生成一个目标类的子类,重写父类的方法并在方法中加入增强逻辑实现动态代理的

注意CGLIB动态代理要求被代理的目标类不能声明为final类,并且方法也不能是final方法,否则无法被CGLIB成功继承和重写

 CGLIB动态代理实现关键步骤
  1. 添加CGLIB库:我们需要将CGLIB依赖引入;
  2. 创建目标对象:创建一个需要被代理的类;
  3. 实现接口:自定义 MethodInterceptor 并重写 intercept() 方法,intercept() 用于增强目标方法,和JDK动态代理的 invoke() 方法类似;
  4. 创建代理对象:通过调用Enhancer对象中的create()方法创建代理对象;
  5. 使用代理对象:利用代理对象来调用目标对象的方法,在方法调用前后会执行额外的逻辑。

演示:

添加依赖

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

创建目标类

package com.example.demo.aspect;

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

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

实现MethodInterceptor接口

package com.example.demo.aspect;

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

import java.lang.reflect.Method;

public class CGLibInterceptor implements MethodInterceptor {
    private Object target;
    public CGLibInterceptor(Object target) {
        this.target = target;
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //跟JDK 代理一样,代理增强的逻辑写在这个方法中
        System.out.println("我是中介,开始代理");
        Object invoke = method.invoke(target, objects);
        System.out.println("我是中介,结束代理");
        return invoke;
    }
}

使用

package com.example.demo.aspect;

import net.sf.cglib.proxy.Enhancer;
public class DynamicMain {
    public static void main(String[] args) {
        HouseSubject target = new RealHouseSubject();
        HouseSubject proxy= (HouseSubject) Enhancer.create(target.getClass(),new CGLibInterceptor(target));
        proxy.rent();
    }
}

 


总结

Spring AOP是如何实现的?

Spring AOP是基于动态代理实现的,有两种方式:基于JDK动态代理实现和基于CGLIB动态代理实现,运行时使用哪种方法与项目配置和代理的对象有关。

Spring AOP的实现方式常见的有几种?

Spring AOP常见的实现方式有两种:1、基于注解@Aspect来实现的;2、基于自定义注解实现,还有一些更原始的方式,如基于代理、基于XML配置等

JDK动态代理和CGLIB动态代理有什么区别以及如何选择?

  • 实现方式:JDK代理是基于接口实现的,必须先定义接口;CGLIB代理是直接基于被代理类实现的,不需要定义接口。
  • 代理机制:JDK动态代理机制是委托机制,通过委托handler调用原始实现类方法;而CGLIB则使用继承机制,被代理类和代理类是继承关系,因此代理类对象可以直接赋值给代理类类型的变量。总的来说:JDK代理是基于接口实现的,CGLIB是基于继承实现的
  • 选择:如果需要代理实现了接口的类,且对性能要求不是特别高,可以选择JDK代理;如果需要代理没有实现接口的类,或者对性能要求较高,可以用CGLIB代理。
  • 限制:JDK动态代理和CGLIB动态代理都不能代理 final 类和方法。对于JDK动态代理,final 类不能实现接口;对于CGLIB动态代理, final 类不能被继承。此外,两者都不能代理 final 方法,因为 final 方法不能被重写。

以上就是本篇所有内容~

若有不足,欢迎指正~ 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot五:JSR303校验

精心整理了最新的面试资料和简历模板&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 松散绑定 意思是比如在yaml中写的是last-name&#xff0c;这个和lastName意思是一样的&#xff0c;-后的字母默认是大写的 JSR303校验 就是可以在字段增加…

uniapp 系统学习,从入门到实战(六)—— 样式与布局

全篇大概 4700 字(含代码)&#xff0c;建议阅读时间 30min &#x1f4da; 目录 Flex 布局在 UniApp 中的应用响应式设计与适配多端使用 SCSS 提升样式开发效率实战案例演示总结 1. Flex 布局在 UniApp 中的应用 1.1 基础布局实现 通过 display: flex 快速构建弹性容器&#…

Redis---缓存穿透,雪崩,击穿

文章目录 缓存穿透什么是缓存穿透&#xff1f;缓存穿透情况的处理流程是怎样的&#xff1f;缓存穿透的解决办法缓存无效 key布隆过滤器 缓存雪崩什么是缓存雪崩&#xff1f;缓存雪崩的解决办法 缓存击穿什么是缓存击穿&#xff1f;缓存击穿的解决办法 区别对比 在如今的开发中&…

从UNIX到Linux:操作系统进化史与开源革命

从UNIX到Linux&#xff1a;操作系统进化史与开源革命 一、操作系统&#xff1a;数字世界的基石 1.1 什么是操作系统&#xff1f; 操作系统&#xff08;OS&#xff09;是计算机系统的核心管理者&#xff0c;承担着三大核心使命&#xff1a; 硬件指挥官&#xff1a;直接管理C…

【Python 语法】算法合集

查找二分查找代码大 O 表示法 广度优先搜索代码 狄克斯特拉算法 递归递归调用栈 分而治之&#xff08;divide and conquer&#xff0c;D&C&#xff09;贪心教室调度问题背包问题集合覆盖问题 动态规划背包问题旅游行程最优化 遇到问题时&#xff0c; 如果不确定该如何 高效…

IDEAPyCharm安装ProxyAI(CodeGPT)插件连接DeepSeek-R1教程

背景&#xff1a;最近DeepSeek比较火嘛&#xff0c;然后在githup上也看到了GitHub Copilot&#xff0c;就想着现在AI的准确率已经可以提高工作效率了。所以从网上找了一些编程插件&#xff0c;发现Proxy支持的模型比较多&#xff0c;通用性和适配性比较好。所以本文记录一下pro…

基于javaweb的SSM+Maven幼儿园管理系统设计和实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…

Java-Lambda表达式详解

引言&#xff1a;为什么需要 Lambda 表达式&#xff1f; 在 Java 8 之前&#xff0c;处理需要传递代码块的场景&#xff08;如事件监听、线程任务&#xff09;通常依赖匿名内部类。这种方式代码冗余&#xff0c;可读性差。例如&#xff1a; // 传统匿名内部类实现 Runnable n…

springboot之集成Elasticsearch

目录 二、Elasticsearch 是什么&#xff1f;三、Elasticsearch 安装四、Springboot 集成 Elasticsearch 的方式五、创建项目集成 Elasticsearch 2.创建 Spring Initializr 项目 es &#xff08;3&#xff09;.新建实体类 User&#xff08;4&#xff09;.新建 dao 接口类 UserR…

HBuilderx 插件开发变量名称翻译 ,中文转(小驼峰,大驼峰,下划线,常量,CSS类名)

HBuilderx 插件开发变量名称翻译 &#xff0c;中文转&#xff08;小驼峰&#xff0c;大驼峰&#xff0c;下划线&#xff0c;常量&#xff0c;CSS类名&#xff09; 插件开发文档 工具HBuilderx &#xff0c;创建项目 创建成功后目录 插件需求 开发时 用来将中文转为&#xff0…

C# 数据转换

1. 文本框读取byte&#xff0c;ushort格式数据 byte addr; if (byte.TryParse(textBoxAddr.Text, out addr) true) {}2. 字节数组 (byte[]) 转换为 ASCII 字符串 byte[] bytes { 72, 101, 108, 108, 111 }; // "Hello" 的 ASCII 码 string s0 Encoding.ASCII.Ge…

unity学习60: 滑动条 和 滚动条 滚动区域

目录 1 滚动条 scrollbar 1.1 创建滚动条 1.2 scrollbar的子物体 1.3 scrollbar的属性 2 滚动视图 scroll View 2.1 创建1个scroll View 2.1.1 实际类比&#xff0c;网页就是一个 scroll view吧 2.2 子物体构成 2.3 核心component : Scroll Rect 3 可视区域 view p…

如何通过 LlamaIndex 将数据导入 Elasticsearch

作者&#xff1a;来自 Elastic Andre Luiz 逐步介绍如何使用 RAG 和 LlamaIndex 提取数据并进行搜索。 在本文中&#xff0c;我们将使用 LlamaIndex 来索引数据&#xff0c;从而实现一个常见问题搜索引擎。 Elasticsearch 将作为我们的向量数据库&#xff0c;实现向量搜索&am…

从黑暗到光明:FPC让盲人辅助眼镜成为视障者的生活明灯!【新立电子】

在科技日新月异的今天&#xff0c;智能技术正以前所未有的方式改变着我们的生活。对于视障人士而言&#xff0c;科技的进步更是为他们打开了一扇通往更加独立自主生活的大门。其中&#xff0c;盲人辅助智能眼镜可以成为视障人士日常生活中的得力助手。FPC在AR眼镜中的应用&…

【MySQL】数据类型与表约束

目录 数据类型分类 数值类型 tinyint类型 bit类型 小数类型 字符串类型 日期和时间类型 enum和set 表的约束 空属性 默认值 列描述 zerofill 主键 自增长 唯一键 外键 数据类型分类 数值类型 tinyint类型 MySQL中&#xff0c;整形可以是有符号和无符号的&…

tableau之标靶图、甘特图和瀑布图

一、标靶图 概念 标靶图&#xff08;Bullet Chart&#xff09;是一种用于显示数据与目标之间关系的可视化图表&#xff0c;常用于业务和管理报告中。其设计旨在用来比较实际值与目标值&#xff0c;同时展示额外的上下文信息&#xff08;如趋势&#xff09;。 作用 可视化目标…

微服务学习(2):实现SpringAMQP对RabbitMQ的消息收发

目录 SpringAMQP是什么 为什么采用SpringAMQP SpringAMQP应用 准备springBoot工程 实现消息发送 SpringAMQP是什么 Spring AMQP是Spring框架下用于简化AMQP&#xff08;高级消息队列协议&#xff09;应用开发的一套工具集&#xff0c;主要针对RabbitMQ等消息中间件的集成…

【JAVA SE基础】抽象类和接口

目录 一、前言 二、抽象类 2.1 抽象类的概念 2.2 抽象类语法 2.3 抽象类特性 2.4 抽象类的作用 三、接口 3.1 什么是接口 3.2 语法规则 3.3 接口使用 3.4 接口特性 3.5 实现多接口 3.6 接口间的继承 四、Object类 4.1 获取对象信息&#xff08; toString() &…

《每天读一个JDK源码》之HashMap解读

&#x1f4cc;《每天读一个JDK源码》之HashMap解读 &#x1f517;源码定位&#xff1a;java.util.HashMap&#xff08;建议IDE对照阅读&#xff09; 今天我们来破解Java集合框架中最精妙的艺术品——HashMap&#xff01;它不仅是面试必考题&#xff08;出现率99%&#xff09;&…

蓝牙接近开关模块感应开锁手机靠近解锁支持HID低功耗

ANS-BT101M是安朔科技推出的蓝牙接近开关模块&#xff0c;低功耗ble5.1&#xff0c;采用UART通信接口&#xff0c;实现手机自动无感连接&#xff0c;无需APP&#xff0c;人靠近车门自动开锁&#xff0c;支持苹果、安卓、鸿蒙系统&#xff0c;也可以通过手机手动开锁或上锁&…