详解Spring中的Aop编程原理JDK动态代理和CGLIB动态代理

😉😉 学习交流群:

✅✅1:这是孙哥suns给大家的福利!

✨✨2:我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料

🥭🥭3:QQ群:583783824   📚📚  工作微信:BigTreeJava 拉你进微信群,免费领取!

🍎🍎4:本文章内容出自上述:Spring应用课程!💞💞

💞💞5:以上内容,进群免费领取呦~ 💞💞💞💞

一:AOP编程

Aop编程全称为:Aspect Oriented Programming面向切面编程,什么叫做面向切面编程?oop面向对象编程,pop面向过程编程。

1:面向对象编程含义

以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用来达到开发目的。

2:面向过程编程

        以过程为基本单位的层程序开发,以过程之间的相互协同,相互调用来达到开发的目的,这里的古城值得就是方法或者说函数

3:面向切面编程

        以切面为基本单位的层程序开发,以切面之间的相互协同,相互调用来达到开发的目的。如果我们能找到切面,并且以切面进行开发,就是以切面进行开发

        切面:切入点+额外功能。他们两个被组装后形成切面。这是spring动态代理的开发当中是面向切面编程的基础。spring的动态代理开发其实就是面向切面编程,这两个是完全等价的,Aop编程的背后的技术就是动态代理技术。

4: 什么是Aop编程

        Aop编程的本质就是Spring动态代理的开发,通过代理为原始类增加额外的功能,好处就是利于原始类的维护,有了Aop编程之后就可以取代oop这个说法是不正确的,她是从oop上发展来的,不存在取代的关系,他仅仅是oop编程的一种有益的补充。

5:Aop的开发步骤

        准备原始对象—准备原始功能-切入点—组装成切面(额外功能+切入点)
        这里的开发步骤和spring的动态代理技术是没有任何区别的。
        切面:切入点+额外功能
        为什么spring的切入点+额外功能叫做切面呢?

        形成了一个切面,所以叫切面。

二:AOP底层实现原理

        AOP底层实现原理也就是动态代理的底层实现原理,搞明白这个事需要知道几个核心问题

1:核心问题提出

         Aop如何帮我们创建这个动态代理类,这个动态代理类是依赖于一门动态字节码技术,动态字节码技术到底是如何通过我们的编码将JVM中的动态代理类创建出来的呢?

        Spring在做动态代理或者Aop编程的时候,有一个非常有意思的事,他是怎么把原始对象的id值,拿到的是动态代理对象的id值的呢?

        以上的问题就等效与:Spring是如何创建和加工动态代理对象的

2:如何创建动态代理类

        Spring创建动态代理类有两种方式,第一种方式是jdk动态代理,第二种是Cglib动态代理

1:JDK动态代理

        这里研究的是动态代理对象的创建,而并不是动态代理类的创建,真正的动态代理类的创建是Proxy.newInstance(....)这个方法执行的时候,在jvm内存中创建的动态代理类,上边这个方法获取的是动态代理对象。

        Spring的动态代理或者说Spring的Aop的代理对象的创建和使用,他的本质也是使用的是JDK动态代理或者cglib动态代理,他的写法已经在是Spring当中固定好了,所以,原始对象必须得是交由Spring进行创建,另外本质的Spring向外暴露的动态代理的实现方式还是对JDK底层动态代理实现或者cglib动态代理实现的封装。

        创建动态代理的三要素:原始对象、额外功能、动态代理和原始类实现相同的接口。

        这个动态代理在这一组参数当中,invocationHandler这是一个接口,这个接口对应了我们的额外功能,在这个接口当中有一个invoke方法,实现了这个接口之后呢,这个接口当中有三个方法,方法中有三个参数,如下,执行任何一个方法(原始方法)都需要三个要素,对象、方法、参数列表,不论是普通调用,或者使用反射,都是这么个情况,所以第二个参数是方法对象,第三个参数是方法的参数数组,还需要第一步创建的原始对象,这样就可以执行原始方法了,这个invoke方法的返回值代表的是原始方法的返回值,这里的这个接口当中的invoke方法是原生jdk提供的动态代理的实现方式,spring对我们暴露的实现方式呢有那个MethodInceptor这个接口,这个接口当中的invoke方法中有一个参数对象,可以执行proceed()方法其实这个就是对原生jdk的一个封装,原生的肯定复杂一点,封装之后的肯定更好用。

        代理对象要和原始对象实现相同的接口,为什么我们动态代理对象要和原始对象实现相同的接口呢,因为都耦合与一个接口才能迷惑调用者呀

        我们创建的是jdk的动态代理,也需要实现和目标类相同的接口,这个条件不是我们来实现,而是proxy.newProxyInstance这个方法类帮我们实现,这里的传入的第二个参数就是干这个用的。提供这个参数是使用,是采用UserService.class对象,就可以使用userService.getClass().getInterfaces();这个形式获取就可以了。

        //创建原始对象
        UserService userService = new UserServiceImpl();
        //创建动态代理基于jdk,创建动态代理基于jdk的方式,需要调用jdk为我们提供的一个工具proxy
        //这个方法的返回值就是这个类的动态代理对象。
        //这个方法需要三个参数,第一个参数


        到这里我们实现动态代理的的三个要素就准备好了,原始对象我们自己创建的

        额外功能由于我们采用的事JDK形式的动态代理,所以我们使用的接口。将额外功能书写在这接口实现类中的invoke方法当中,进行响应的处理,实现同样的接口,就是获取原始对象实现的接口传递给这个方法,然而在这个Proxy.newProxyInstatnce()这个方法当中还有一个来加载器对象这样的一个参数,这样的一个参数究竟作用是什么呢,这就是
        类加载器的作用:(单纯谈类加载器的作用)
        1:通过类加载器把对应类的字节码文件加载到虚拟机当中,
        2:通过类加载器创建类的class对象,进而创建这个类的实例。
        Class对象记录着这个类最完整的信息。
        我们书写的java文件是java的源码文件,java文件经过编译之后的文件称为字节码文件,也就是.class文件,程序的运行是class文件加载到jvm内存当中之后被执行的结果,那么将class字节码文件加载到虚拟机当中是谁干的呢? 是类加载器,字节码进入到虚拟机当中之后,类加载器帮我们加载class文件到jvm内存之后,还会帮助我们创建这个类的Class对象,这里边有这个类最全的信息,进而可以创建这个类的实例。这个类加载器在我们的创建对象的过程当中是尤为重要的,类加载器这么重要,如何获取类加载器呢?
        虚拟机会为每一个class分配对应的类加载器,有了这个类加载器之后呢,我们就可以创建这个类的字节码对象了,在动态代理的创建中为什么需要类加载器呢?动态代理也是在虚拟机当中获得动态代理类,进而帮我们创建动态代理对象,这个动态代理类没有源文件,也买有字节码文件,动态代理类他是怎么获取这个类的字节码来创建对象的呢?动态代理是通过动态字节码技术来创建字节码,直接就将字节码写到了虚拟机当中,没有一个加载字节码的过程,这个动态字节码技术在我们的虚拟机当中是怎么实现的?我们是怎么对他进行编程的呢?他暴露给我们程序员的就是Proxy.newProxyInstance()这个方法,这个方法中就会使用这个动态字节技术,作为这个动态字节码技术,而且我们在这个方法中已经传入了所有的创建动态代理类所需要的原材料,那么使用这个动态字节码技术呢,直接将字节码写到了虚拟机当中,所以呢类加载的第一个作用就没有用武之地了,这里的类加载器起的作用个呢是床架你这个动态代理类的Class对象,也就是发挥他的第二个技术,省略了一个加载的过程,有了这个字节码对象我们就能创建这个类的实例了(所有的创建对象都是如此),到了这里呢,就不太好搞了,之前的普通类加载的时候呢是虚拟机根据字节码文件(.class文件)进行分配,现在呢,动态代理类是动态字节码,并没有这个Class文件,没有这个class文件,虚拟机就不会为他分配类加载器,也就没有办法生成Class对象,此时在动态代理创建的过程当中,需要类加载器创建代理类的Class对象,可是动态代理类没哟对应的Class问文件,虚拟机也就不会为他分配类加载器,但是我们又需要,怎么办?借一个,借用的是源文件的类加载对象,来过来为动态代理类创建Class对象的,进而创建这个类的实例,这也是这里为什么需要一个类加载器参数的原因。借用谁的?比方说UserService,或者UserServiceImpl

package com.spring;

import com.spring.proxy.User;
import com.spring.proxy.UserService;
import com.spring.proxy.UserServiceImpl;

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

/**
 * @Auther: DaShu
 * @Date: 2021/6/23 19:23
 * @Description:
 */
public class JdkProxyTest {
    public static void main(String[] args) {
        //创建原始对象
        UserService userService = new UserServiceImpl();
        //创建动态代理基于jdk,创建动态代理基于jdk的方式,需要调用jdk为我们提供的一个工具proxy
        //这个方法的返回值就是这个类的动态代理对象。
        //这个方法需要三个参数,第一个参数
        //为了简单采用内部类的方式来实现invocationhANDLER

        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("-----------这里是代理对象中的额外功能-------------");
                //目标方法执行
                Object invoke = method.invoke(userService, args);
                System.out.println("-----------这里是代理对象中的额外功能-------------");
                return invoke;
            }
        };
        UserService proxyUserService = (UserService) Proxy.newProxyInstance(
                JdkProxyTest.class.getClassLoader(),
                userService.getClass().getInterfaces(),
                invocationHandler);
        proxyUserService.login("崔磊磊", "12345");
        proxyUserService.register(new User());
        //-----------这里是代理对象中的额外功能-------------
        //UserServiceImpl.login
        //-----------这里是代理对象中的额外功能-------------
        //-----------这里是代理对象中的额外功能-------------
        //UserServiceImpl.register
        //-----------这里是代理对象中的额外功能-------------

    }
}


        我们说这个了类的类加载器是借的,我们借的谁的都行。

        注意:jdk8.0之前呢 内部类访问外部类类的变量需要外部类的成员边量是final修饰的,jdk8.0之后呢会自动加上这个final,

2:CGLIB动态代理

        cglib动态代理也是spring底层默认支持的一种动态代理创建方式,他跟jdk动态代理的区别在哪里呢?
        jdk动态代理中:原始对象需要实现一个接口,比方说UserServiceImpl 实现 UserService接口,代理对象需要和原始兑现实现相同的接口,为什么会有一个这样的要求呢?第一保证代理类和原始类方法一致,第二点保证代理类当中提供对应方法的新的实现,就可以加入额外功能了,也可以调用原始方法,所以说我们的代理类,这样代理的诉求就实现了。这就是jdk的动态代理。
作为cglib来讲创建动态代理类有什么特点呢?原始类没有实现任何接口,没有实现任何接口的这样的代理类创建代理对象的时候,反正我们的初衷就是我们的代理类和原始类中必须有相同的方法,新的实现里边包括这个额外功能和原始功能的调用,新的代理类可以使用什么来开发呢?cglib要求他所创建的这个代理类要去继承这个原始类,这样来保证你有的这个方法我也有,变相也保证了代理类和原始类有这个共同方法的要求,然后就有机会添加新的实现,调用原始方法super.就可以了,这样的实现的方式不同,但是最终的结果是一致的。cglib采用的事这个父子继承的关系,不管原始类有没有实现接口。


        cgilib实现动态代理的的步骤,1:准备原始对象UserService原始对象的userService不需要实现任何的接口,里边准备两个原始方法,第二部,创建代理对象,创建代理对象需要cgilib对应的jar包,这个jar包spring已经默认帮我们引入进来了,这样我们就可以直接使用,cgilib的jar包中最为关键的类是一个Enhancer的接口,作为这个类来讲,我们调用create方法进行创建代理对象,这里边需要几个参数,他所需要的参数和jdk的参数是一模一样的,只不过这里边的接口类型指定的值父类的class对象,还有一个父类的原始功能,Enhancer.setClassLoader()Enhance.setSupperClass,Enhance.setCallBack(MethodInterceptor)这个接口是cgilib中的接口跟当时的spring不是一个接口,
创建这个cglib的Methodinterceptor的接口的时候,采用一个内部类的形式,创建他的子类的实例对象,子类对象需要重写他的intereceptor方法,
 

package com.spring.cglib;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @Auther: DaShu
 * @Date: 2021/6/24 19:41
 * @Description:
 */
public class TestCglib {
    public static void main(String[] args) {
        UserService userService = new UserService();

        //通过cgilib方式进行创建,
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(TestCglib.class.getClassLoader());
        enhancer.setSuperclass(userService.getClass());
        MethodInterceptor interceptor = new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("----------cgilib日志额外功能。----------");
                //等同于invocationHandler中的invoke方法,
                Object invoke = method.invoke(userService, args);
                return invoke;
            }
        };
        enhancer.setCallback(interceptor);
        UserService proxyUserService = (UserService)enhancer.create();
        proxyUserService.login();
        proxyUserService.register();
        //----------cgilib日志额外功能。----------
        //UserService.login
        //----------cgilib日志额外功能。----------
        //UserService.register
    }
}

///**
// * @Auther: DaShu
// * @Date: 2021/6/24 19:39
// * @Description: 这个代表原始类。
// */
//public class UserService {
//    public void login(){
//        System.out.println("UserService.login");
//    }
//    public void register(){
//        System.out.println("UserService.register");
//    }
//}

       JDK动态代理,Proxy.newProxyInstance()通过接口原始对象的代理类,cgilib动态代理依赖于Eahancer这个类,通过继承原始类创建原始类的代理类,在spring的后续过程中,如果在面试过程中可以把这些方法中的对应的参数说清楚,这是一个大大的加分项。

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

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

相关文章

HuggingFace学习笔记--BitFit高效微调

1--BitFit高效微调 BitFit,全称是 bias-term fine-tuning,其高效微调只去微调带有 bias 的参数,其余参数全部固定; 2--实例代码 from datasets import load_from_disk from transformers import AutoTokenizer, AutoModelForCaus…

【Pytorch】Visualization of Feature Maps(5)——Deep Dream

学习参考来自: PyTorch实现Deep Dreamhttps://github.com/duc0/deep-dream-in-pytorch 文章目录 1 原理2 VGG 模型结构3 完整代码4 输出结果5 消融实验6 torch.norm() 1 原理 其实 Deep Dream大致的原理和【Pytorch】Visualization of Feature Maps(1&…

一起学docker系列之十七Docker Compose 与手动操作的比较与优势分析

目录 1 前言2 不使用 Docker Compose2.1 启动 MySQL 容器2.2 启动 Redis 容器2.3 启动微服务容器 3 使用 Docker Compose4 使用 Docker Compose 的优势5 结语参考地址 1 前言 在当今容器化应用的开发与部署中,容器编排工具的选择对于简化流程、提高效率至关重要。本…

蓝桥杯第1037题子串分值和 C++ 字符串 逆向思维 巧解

题目 思路和解题方法 方案一——遍历哈希表 仅能过60%样例,大多数同学都用的该方法&#xff0c;就不过多赘述 #include <iostream> #include <unordered_map> using namespace std; int main() {string s;cin >> s;int n s.size();int res n;for (int i 0…

未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序报错的解决办法

当在本地计算机上使用Microsoft Office相关库时&#xff0c;可能会出现“未在本地计算机上注册microsoft.ACE.oledb.12.0”提供程序的报错。这是由于缺少相关的驱动程序或者未安装相应的软件所导致的。下面是解决该问题的完整攻略。 可能是因为没有安装数据访问组件&#xff0…

反序列化漏洞(二)

目录 pop链前置知识&#xff0c;魔术方法触发规则 pop构造链解释&#xff08;开始烧脑了&#xff09; 字符串逃逸基础 字符减少 字符串逃逸基础 字符增加 实例获取flag 字符串增多逃逸 字符串减少逃逸 延续反序列化漏洞(一)的内容 pop链前置知识&#xff0c;魔术方法触…

树基本概念+前中后序遍历二叉树

&#x1f308;一、树的基本概念 ☀️1.树的定义&#xff1a;树是一种非线性结构&#xff0c;看起来像一棵倒挂的树&#xff0c;根朝上&#xff0c;而叶朝下。 ☀️2.相关术语 1.根节点&#xff1a;图中的A&#xff0c;无前驱结点 2.叶节点&#xff08;终端节点&#xff09;&a…

iptables——建立linux安全体系

目录 一. 安全技术类型 二. linux防火墙 1. 按保护范围划分&#xff1a; 2. 按实现方式划分&#xff1a; 3. 按网络协议划分&#xff1a; 4. 防火墙原理 三. 防火墙工具——iptables 1. netfilter 中五个勾子函数和报文流向 数据包传输过程&#xff1a; ① .五表四链…

设计模式-结构型模式之外观设计模式

文章目录 七、外观模式 七、外观模式 外观模式&#xff08;Facade Pattern&#xff09;隐藏系统的复杂性&#xff0c;并向客户端提供了一个客户端可以访问系统的接口。它向现有的系统添加一个接口&#xff0c;来隐藏系统的复杂性。 这种模式涉及到一个单一的类&#xff0c;该类…

爬虫-xpath篇

1.xpath的基础语法 表达式描述nodename选中该元素/从根节点选取、或者是元素和元素间的过渡//从匹配选择的当前节点选择文档中的节点&#xff0c;而不考虑它们的位置.选取当前节点…选取当前节点的父节点选取属性text()选取文本 举例&#xff1a; 路径表达式结果html选择html元…

使用java批量生成Xshell session(*.xsh)文件

背景 工作中需要管理多套环境, 有时需要同时登陆多个节点, 且每个环境用户名密码都一样, 因此需要一个方案来解决动态的批量登录问题. XShell Xshell有session管理功能: 提供了包括记住登录主机、用户名、密码及登录时执行命令或脚本(js,py,vbs)的功能 session被存储在xsh文…

6-49.自定义的学生类

本题要求定义一个简单的学生类&#xff0c;数据成员仅需要定义学号和姓名&#xff0c;函数成员的原型见给出的代码&#xff0c;请给出函数成员的类外完整实现。 其中m_id和m_name分别表示学生的学号和姓名&#xff0c;类型已经定义好。类内声明了3个成员函数&#xff0c;分别表…

Linux docker批量安装软件

1.前提 具备docker-compose.yml 和 prometheus.yml 文件 常见报错&#xff1a; 1.没有配置network 配置network即可&#xff1a; 2.缺少相关依赖 docker-compose.yml加入相关配置 3.重复项 删除掉重复的 最后 执行 等待完成 下载后相当于有了这些软件包的镜像 启动的每…

大数据Hadoop-HDFS_架构、读写流程

大数据Hadoop-HDFS 基本系统架构 HDFS架构包含三个部分&#xff1a;NameNode&#xff0c;DataNode&#xff0c;Client。 NameNode&#xff1a;NameNode用于存储、生成文件系统的元数据。运行一个实例。 DataNode&#xff1a;DataNode用于存储实际的数据&#xff0c;将自己管理…

OpenHarmony亮相MTSC 2023 | 质量效率共进,赋能应用生态发展

11月25日&#xff0c;MTSC 2023第十二届中国互联网测试开发大会在深圳登喜路国际大酒店圆满举行。大会以“软件质量保障体系和测试研发技术交流”为主要目的&#xff0c;旨在为行业搭建一个深入探讨和交流的桥梁和平台。OpenAtom OpenHarmony&#xff08;简称“OpenHarmony”&a…

Langchain-Chatchat的安装过程

参考&#xff1a;LLMs之RAG&#xff1a;LangChain-Chatchat(一款中文友好的全流程本地知识库问答应用)的简介(支持 FastChat 接入的ChatGLM-2/LLaMA-2等多款主流LLMs多款embe_一个处女座的程序猿的博客-CSDN博客 1、安装过程中出现了 GPU驱动版本 是11.8 而 python -c "…

文心版吴恩达课程:语义核心(Semantic Kernel)插件的商业应用

文心版吴恩达课程&#xff1a;语义核心&#xff08;Semantic Kernel&#xff09;插件的商业应用 Semantic Kernel is an SDK that integrates Large Language Models (LLMs) like OpenAI, Azure OpenAI, and Hugging Face with conventional programming languages like C#, P…

HTTP 基本概念(计算机网络)

一、HTTP 是什么&#xff1f; HTTP(HyperText Transfer Protocol) &#xff1a;超文本传输协议。 HTTP 是一个在计算机世界里专门在「两点」之间「传输」文字、图片、音频、视频等「超文本」数据的「约定和规范」。 「HTTP 是用于从互联网服务器传输超文本到本地浏览器的协议…

【海思SS528 | VDEC】MPP媒体处理软件V5.0 | VDEC的使用总结

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

SQL简介

目录 一、SQL 简史 二、数据库简史 1、Dr. Codds 对关系型数据库系统的十二条规则 2、设计数据库的结构 3、数据库的前景 4、对于什么是客户机/服务器型电脑系统 BernardH.Boar的定义如下&#xff1a; 5、交互式语言 6、易于实现 7、SQL 总览 三、流行的 SQL 开发工具…