从根上理解Cglib与JDK动态代理

news2024/10/2 7:21:28

最近在阅读到了Spring源码对于两种动态代理使用在不同场景下的使用,两种方式各有利弊写一篇文加深自己的认识。文中对于源码的涉及较少,更多的是作者自己的理解和举例,然后通过部分源码验证。

首先看两个面试经常会遇到的关于Spring的问题:

  1. @Configuration和@Component注解的不同
  • @Configuration修饰的类会被Cglib动态代理,在类内部方法相互调用添加了@Bean注解的方法时通过在切面方法中调用getBean()方法来保证调用该方法返回的都是同一个实例
  • @Component修饰的类不会被代理,每次方法内部调用都会生成新的实例,这样就不能保证其生成的对象是一个单例对象。
  1. @Transactional失效的原因
  • @Transactional可以JDK或Cglib动态代理实现的事务(默认JDK),在Bean创建时如果检测到类中有@Transactional就会对其进行动态代理,如果类内部没有被@Transactional修饰的方法中调用了其它被@Transactional修饰的内部方法,那么此时事务注解是不会生效的,原因在于只有外部调用才会走代理增强逻辑而内部类的互相调用只是原对象的方法调用,没有经过代理类。

其实上面可以看出出Spring在使用两种代理方式时的不同处理:@Configuration修饰的类被Cglib动态代理后,类内部方法调用也可以走增强逻辑,而含有@Transactional注解的类无论是Cglib还是JDK动态代理都不能进行方法内部的相互调用。

两种代理方式的调用逻辑

JDK动态代理

标准的用法

public interface TestInterface {
    void sayHello();
}

public static class Test implements TestInterface {
    @Override
    public void sayHello() {
        System.out.println("hello");
    }
}

//需要定义一个实现了InvocationHandler接口的对象,目的是获取被代理类的实例和自定义增强方法
public static class MyInvocationHandler implements InvocationHandler{
    //被代理类的实例对象
    protected Object target;
    public MyInvocationHandler(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);
        return result;
    }
}

public static void main(String[] args) {
    Test test = new Test();
    TestInterface testInterface = (TestInterface) Proxy.newProxyInstance(test.getClass().getClassLoader(), Test.class.getInterfaces(), new MyInvocationHandler(test));
    testInterface.sayHello();
}
复制代码

下面是代理类的逻辑代码,这个代理类并不是用反编译内存中的代理类来获取得,是作者自己整了一个类似的,如果要获取真正的代理类代码网上方法很多

//代理类的父类,里面有生成代理类的主要逻辑
public static class Proxy{
    //被代理对象实例的调用对象
    protected InvocationHandler h;
}
//生成的代理类继承Proxy主要是为了使用父类中的InvocationHandler对象来调用被代理类对象的目标方法
//实现共同接口是为了获取需要增强的目标方法
public static class TestProxy extends Proxy implements TestInterface{
    protected TestProxy(InvocationHandler h) {
        super(h);
    }
    @Override
    public void sayHello() {
        try {
            //这里对获取接口方法做了简化处理
            //调用父类中存储的被代理对象的handler执行代理逻辑
            super.h.invoke(this, this.getClass().getInterfaces()[0].getMethods()[0],null);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
}
复制代码

逻辑图

Cglib动态代理

标准的用法

public static class Test implements TestInterface {
    @Override
    public void sayHello() {
        System.out.println("hello");
    }
}

//实现MethodInterceptor接口注册回调函数对代理类中所有方法进行拦截增强
public static class MyInvocationHandler implements MethodInterceptor {

    //o为继承了被代理类的代理类对象,method为执行方法,objects为方法参数
    //methodProxy为代理对象方法,其中有被代理方法和代理方法的映射关系
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("增强方法");
        return methodProxy.invokeSuper(o,objects);
    }
}

public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Test.class);
    enhancer.setCallback(new MyInvocationHandler());
    TestInterface testInterface = (TestInterface)enhancer.create();
    testInterface.sayHello();
}
复制代码

动态生成代理类的伪代码,省略了很多很多细节

//Cglib中都是通过代理类中的方法来替换被代理类,然后直接调用代理类对象的方法即可
public static class TestProxy extends Test{
    public final void sayHello() {
        System.out.println("增强方法");
        super.sayHello();
    }
}
复制代码

逻辑图

==============================================================

从上面可以看出Cglib和JDK最大的区别在于Cglib实现的动态代理并没有被代理类的实例对象,所有的方法调用都是通过代理类来实现的(子类方法 -> 增强逻辑 -> 子类代理方法 -> 父类方法),而JDK则同时生成了被代理类和代理类的实例对象,然后在代理类中保存有被代理类的引用,目标方法的调用还是被代理对象执行的。Cglib方法调用时是使用代理类对象内部方法的相互调用实现的,由于代理类的所有方法都进行了改写,所以内部调用也会被增强,JDK方法调用时是代理类对象和被代理类对象间方法的相互调用实现的,只有通过调用代理类对象的代理方法时才会走增强逻辑,而如果是被代理对象自己的内部调用,被代理对象方法没有改变,所以无法增强。 理解了这一点再看Spring动态代理的使用就好理解了

Spring源码验证

调用@Configuration注解的类时会用到的代理类拦截器

//Spring中Enhancer对象注册的三种拦截器
//回调数组,根据CALLBACK_FILTER中accept方法返回的索引值去从该数组中选择对应的Callback
private static final Callback[] CALLBACKS = new Callback[] {
      new BeanMethodInterceptor(),
      new BeanFactoryAwareMethodInterceptor(),
      NoOp.INSTANCE
};

//BeanMethodInterceptor的intercept方法,对父类中所有带有@Bean注解的方法都进行拦截增强
//无论是Spring通过反射实例化Bean还是配置类中方法的内部调用,都会通过BeanFactory来生成和获取Bean实例
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
         MethodProxy cglibMethodProxy) throws Throwable {
   //是否为Spring通过反射调用 
   if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
      //调用父类方法生成新的Bean对象,并将其注册到Spring上下文中
      return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
   }
   
   //类方法的内部调用,从BeanFactory中获取bean
   //即使通过内部方法直接调用为能保证获取的对象为同一实例
   return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName);
}
复制代码

Cglib对于@Transactional注解采用的代理类拦截器DynamicAdvisedInterceptor

@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
   Object oldProxy = null;
   boolean setProxyContext = false;
   Class<?> targetClass = null;
   Object target = null;
   try {
      if (this.advised.exposeProxy) {
         oldProxy = AopContext.setCurrentProxy(proxy);
         setProxyContext = true;
      }
      //Spring缓存了被代理类的实例
      //获取被代理类实例
      target = getTarget();
      if (target != null) {
         targetClass = target.getClass();
      }
      //获取目标方法的拦截器链,被@Transactional修饰的方法会有缓存方法和调用链关系
      List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
      Object retVal;
      if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
         Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
         //没有被@Transactional修饰的方法会直接调用被代理类本身来执行
         //此处和Cglib通用的处理不一样,Spring缓存和被代理实例,用被代理类实例来执行方法
         //所以未被注解修饰的方法调用注解修饰的方法不能触发拦截器
         retVal = methodProxy.invoke(target, argsToUse);
      }
      else {
         //@Transactional修饰的方法会通过代理类对象来执行,进入拦截器执行增强逻辑
         retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
      }
      retVal = processReturnType(proxy, target, method, retVal);
      return retVal;
   }
   finally {
      if (target != null) {
         releaseTarget(target);
      }
      if (setProxyContext) {
         AopContext.setCurrentProxy(oldProxy);
      }
   }
}
复制代码

JDK的处理逻辑同样是调用被代理类来执行未加@Transactional注解的方法,就不多写了。

小结

Cglib动态代理与JDK动态代理的区别本质上应该对于代理对象的调用方式有差别,Cglib是直接将代理类对象作为目标对象使用,增强逻辑直接写入代理类的子类方法中,调用方法时只需一个代理类对象即可,而JDK则是将被代理类对象引用存放在代理类对象中,增强逻辑在代理对象中存放而实际执行方法还需要调用被代理对象。当然Cglib通过缓存被代理类的实例对象也可以做到JDK的效果。
两种代理方式一个通过继承获取类方法信息,一种通过接口获取类方法信息,在理解其原理后,如何选型使用还是看业务场景和两种方式的执行效率来决定。

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

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

相关文章

数理统计笔记7:分类数据分析-拟合优度检验和列联分析

引言 数理统计笔记的第7篇介绍了分类数据分析的方法&#xff0c;包括拟合优度检验和列联分析&#xff0c;给出了两者的卡方检验量的表达式&#xff0c;并且用例子进行了说明&#xff0c;最后谈了列联分析需要注意的问题。 引言什么是分类型数据拟合优度检验χ2\chi^2χ2统计量例…

使用AWS-AppSync实时监控物联网设备iOS端

aws-appsync-iot-core-实时iOS端示例 第一步参考如下资料 第一步找资料 网址&#xff1a; https://aws.amazon.com/cn/blogs/mobile/iot-with-aws-appsync/ iOS的sdk是 &#xff1a; https://github.com/aws-amplify/aws-sdk-ios android的sdk是&#xff1a; https://gith…

[附源码]JAVA毕业设计基于web的公益募捐网站(系统+LW)

[附源码]JAVA毕业设计基于web的公益募捐网站&#xff08;系统LW&#xff09; 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目…

Apache JMeter压测工具

1.工具介绍 是什么 JMeter是一个软件&#xff0c;使负载测试或业绩为导向的业务&#xff08;功能&#xff09;测试不同的协议或技术。 Apache软件基金会的Stefano Mazzocchi JMeter的最初的开发。他写道&#xff1a;它主要对 Apache JServ&#xff08;现在称为如Apache Tomcat…

项目管理工具dhtmlxGantt入门教程(一):如何安装dhtmlxGantt

您可以使用 NuGet 、 Bower 或 npm 包管理器将 dhtmlxGantt 包安装到您的项目中,也可以从 CDN 中包含必要的 JS/CSS 文件。 DhtmlxGantt正版试用下载&#xff08;qun&#xff1a;764148812&#xff09;https://www.evget.com/product/4213/download NuGet 安装 dhtmlxGantt …

C++使用gRPC实例

什么是gRPC RPC 即远程过程调用协议&#xff08;Remote Procedure Call Protocol&#xff09;&#xff0c;可以让我们像调用本地对象一样发起 远程调用。RPC 凭借其强大的治理功能&#xff0c;成为解决分布式系统通信问题的一大利器。 gRPC是一个现代的、高性能、开源的和语言…

Python数据分析实战-实现一维列表(数组)和多维列表(数组)的相互转化(附源码和实现效果)

前面我介绍了可视化的一些方法以及机器学习在预测方面的应用&#xff0c;分为分类问题&#xff08;预测值是离散型&#xff09;和回归问题&#xff08;预测值是连续型&#xff09;&#xff08;具体见之前的文章&#xff09;。 从本期开始&#xff0c;我将做一个数据分析类实战…

[附源码]计算机毕业设计springboot校园疫情管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

文件的上传与下载

文章目录一、 实验目的&#xff1a;二、实验要求&#xff1a;三、实验内容&#xff1a;1、单文件上传2、多文件上传3、上传文件的大小限定4、实现文件的下载功能&#xff0c;并解决下载乱码问题一、 实验目的&#xff1a; 掌握Servlet的HttpServletRequest对文件上传的支持&am…

内存管理---分页机制

目录 物理内存管理带来的问题 直接映射 一级页表 二级页表 参考&#xff1a; &#xff08;C语言内存七&#xff09;分页机制究竟是如何实现的&#xff1f; - Smah - 博客园 物理内存管理带来的问题 比如4GB的flash, 如果应用程序可直接访问物理内存&#xff0c;那么可能一个…

云小课|基于华为云WAF的日志运维分析,构筑设备安全的城墙

阅识风云是华为云信息大咖&#xff0c;擅长将复杂信息多元化呈现&#xff0c;其出品的一张图(云图说)、深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手华为云。更多精彩内容请单击此处。 摘要&#xff1a;云日志服务用于收集来自主机和云服务的日志数据&#x…

windows安装docker版青龙面板

1.下载docker Docker Desktop 官方下载地址&#xff1a; windows docker 2 管理员运行PowerShell&#xff0c;执行下面的命令(Hyper-V 和容器特性) Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All3.运行第一步下载好的exe 双击下载的 Docker fo…

springboot基于vue.js的掌上博客系统的设计与实现毕业设计源码063131

Springboot掌上博客系统的设计与实现 摘 要 掌上博客系统是当今网络的热点&#xff0c;博客技术的出现使得每个人可以零成本、零维护地创建自己的网络媒体&#xff0c;Blog站点所形成的网状结构促成了不同于以往社区的Blog文化&#xff0c;Blog技术缔造了“博客”文化。 本文课…

恶补了 Python 装饰器的八种写法,你随便问~

对于每一个学习 Python 的同学&#xff0c;想必对 符号一定不陌生了&#xff0c;正如你所知&#xff0c; 符号是装饰器的语法糖&#xff0c;符号后面的函数就是我们本文的主角&#xff1a;装饰器。 装饰器放在一个函数开始定义的地方&#xff0c;它就像一顶帽子一样戴在这个…

Java---File详解

目录 一、File的概述 二、File的创建 三、File的常见成员方法 1&#xff1a;判断和获取 2&#xff1a;创建和删除 &#xff08;1&#xff09;createNewFile() &#xff08;2&#xff09;mkdir() &#xff08;3&#xff09;delete&#xff08;&#xff09; 3&#xff1a…

高新技术企业认定条件

主要是评估企业的以下几个条件&#xff1a; 一是企业成立满一年以上&#xff0c;并且符合高企八大领域&#xff1b; 二是拥有核心自主知识产权&#xff0c;并且近一年高新产品的收入啊&#xff0c;需要占总收入的60%以上&#xff1b; 三是科技人员占当年职工总数的比例不低于…

mybatis学习:四、关联查询、缓存

7. 关联查询 7.1 准备工作: 数据库表: #订单表&#xff1a; create table tb_order (id INT AUTO_INCREMENT PRIMARY KEY,userid INT,createtime DATETIME,state VARCHAR(20) ) ENGINEInnoDB AUTO_INCREMENT1;#订单详情表 CREATE TABLE tb_orderdetail(…

[附源码]Python计算机毕业设计SSM酒店式公寓服务系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

[LeetCode 1769]移动所有球到每个盒子所需的最小操作数

题目描述 题目链接&#xff1a;[LeetCode 1769]移动所有球到每个盒子所需的最小操作数 有 n 个盒子。给你一个长度为 n 的二进制字符串 boxes &#xff0c;其中 boxes[i] 的值为 ‘0’ 表示第 i 个盒子是 空 的&#xff0c;而 boxes[i] 的值为 ‘1’ 表示盒子里有 一个 小球。…

Gradle简单配置

写在前面&#xff1a;一开始配置gradle的时候&#xff0c;最好不要把他maven仓库配置在一起&#xff0c;前段时间配置公司项目身心俱疲。大部分公司里面用gradle都是配置的自己的仓库地址&#xff0c;所以有的东西就不要和网上的配置保持一致了&#xff0c;比如说这个仓库地址。…