spring内嵌cglib包,这里藏着一个大坑

问题发现

2022-01-21 早上 9 点,订单系统出现大面积的”系统未知错误”报错,导致部分用户无法正常下单。查询后台日志,可以看到大量的 duplicate class attempt。

java.lang.LinkageError-->loader (instance of  org/springframework/boot/loader/LaunchedURLClassLoader): attempted  duplicate class definition for name: "com/order/vo/OrderAndExtendVO$$BeanMapByCGLIB$$e8178b2a"
StackTrace:
org.springframework.cglib.core.CodeGenerationException: java.lang.LinkageError-->loader (instance of  org/springframework/boot/loader/LaunchedURLClassLoader): attempted  duplicate class definition for name: "com/order/vo/OrderAndExtendVO$$BeanMapByCGLIB$$e8178b2a"
    at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:538)
    at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:110)
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:108)
    at org.springframework.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61)
    at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34)
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:134)
    at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319)
    at org.springframework.cglib.beans.BeanMap$Generator.create(BeanMap.java:127)
    at org.springframework.cglib.beans.BeanMap.create(BeanMap.java:59)
    ····省略其他堆栈

问题分析

首先,通过堆栈,可以初步判断,报错是 cglib 尝试生成一个已经存在的 class 导致的。

代码中调用了 BeanMap.create(Object)方法,这个方法会生成动态代理类。我们直接进入到 AbstractClassGenerator.create(Object)的源码,可以看到,全局缓存里已经有了就不会再次生成,按理来说,代理类并不会重复生成,难道缓存失效了吗?

一开始我怀疑是因为缓存被禁用了。但是吧,这个 useCache 字段只能通过 AbstractClassGenerator.setUseCache(boolean)方法设置,而整个项目并没有任何地方引用到这个方法,所以,这个假设并不成立。

abstract public class AbstractClassGenerator implements ClassGenerator {
    // 是否使用缓存
    private boolean useCache = true;
    private static final Object NAME_KEY = new Object();
    // 缓存是全局的
    private static final Source SOURCE = new Source(BeanMap.class.getName());
    protected static class Source {
        String name;
        /*
         * 全局缓存,格式为:
         *
         * {
         *      "classLoader1":{
         *          NAME_KEY:["className1","className2"],
         *          "className1":class1,
         *          "className2":class2
         *      },
         *      "classLoader2":{
         *          NAME_KEY:["className2","className3"],
         *          "className3":class3,
         *          "className2":class2
         *      }
         * }
         * zzs001
         *
         */
        Map cache = new WeakHashMap();
        public Source(String name) {
            this.name = name;
        }
    }

    protected Object create(Object key) {
        try {
            Class gen = null;

            synchronized (source) {
                ClassLoader loader = getClassLoader();
                Map cache2 = null;
                cache2 = (Map)source.cache.get(loader);
                if (cache2 == null) {
                    cache2 = new HashMap();
                    cache2.put(NAME_KEY, new HashSet());
                    source.cache.put(loader, cache2);
                } else if (useCache) {
                    Reference ref = (Reference)cache2.get(key);
                    gen = (Class) (( ref == null ) ? null : ref.get());
                }
                if (gen == null) {
                    Object save = CURRENT.get();
                    CURRENT.set(this);
                    try {
                        this.key = key;

                        if (attemptLoad) {
                            try {
                                gen = loader.loadClass(getClassName());
                            } catch (ClassNotFoundException e) {
                                // ignore
                            }
                        }
                        if (gen == null) {
                            byte[] b = strategy.generate(this);
                            String className = ClassNameReader.getClassName(new ClassReader(b));
                            getClassNameCache(loader).add(className);
                            gen = ReflectUtils.defineClass(className, b, loader);
                        }

                        if (useCache) {
                            cache2.put(key, new WeakReference(gen));
                        }
                        return firstInstance(gen);
                    } finally {
                        CURRENT.set(save);
                    }
                }
            }
            return firstInstance(gen);
        } catch (RuntimeException e) {
            throw e;
        } catch (Error e) {
            throw e;
        } catch (Exception e) {
            throw new CodeGenerationException(e);
        }
    }
}

我突然想到,这个 cglib 只是 spring 内嵌的 cglib,并不是”真正的 cglib”。缓存只是在当前的这个 cglib 生效,如果原生的 cglib 也要创建这个类,是不是就会报错了呢?

通过查看引用,项目里确实存在这种情况:使用了不同的 cglib 来创建同一个类:

spring内嵌cglib包,这里藏着一个大坑

接着我用代码试验了一下,果然出现了同样的报错:

spring内嵌cglib包,这里藏着一个大坑

问题解决

于是,我们可以给出结论: 使用了spring 和原生两个不同的 cglib 来生成同一个 class,会因为缓存无法共享而出现 duplicate class attempt 的报错

知道了原因,解决的办法就非常简单了。只要把 cglib 的导包改成同一个就行了。

修复后,生产再无该类报错,基本证明我们是对的。

结语

以上就是这次生产报错的处理过程。这里我有几个疑惑的地方:

  1. cglib 判断一个 class 是否存在,为什么不直接检查项目里的 class?却要用缓存这种不可靠的手段?
  2. spring 为什么不直接依赖 cglib?而要自己内嵌一个?

最后,感谢阅读,欢迎交流、指正。

本文为原创文章,转载请附上原文出处链接:https://www.cnblogs.com/ZhangZiSheng001/p/15838657.html

Original: https://www.cnblogs.com/ZhangZiSheng001/p/15838657.html
Author: 子月生
Title: spring内嵌cglib包,这里藏着一个大坑

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/575770/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 实现随机验证码

    Java实现随机验证码的生成 随机验证码: 法一:普通方法 核心逻辑: 1.定义一个String类型的变量存储验证码字符。 2.定义一个for循环,循环n次(n为验证码的所需要字符…

    数据库 2023年6月16日
    0148
  • jmeter的一些概念知识

    前言 一、Jmeter的作用 – 1.jmeter进行接口操作 2. jmeter进行性能操作 二、Jmeter的一些概念的理解 – 1.事务 2. TPS…

    数据库 2023年6月6日
    0138
  • 二手车价格预测 | 构建AI模型并部署Web应用 ⛵

    💡 作者:韩信子@ShowMeAI📘 数据分析实战系列:https://www.showmeai.tech/tutorials/40📘 机器学习实战系列:https://www.s…

    数据库 2023年6月14日
    0113
  • Linux 7安装Mysql5.7版本

    Mysql 5.7的安装搭建 首先去到官方网站的下载链接中找到对应你Linux服务器版本的mysql软件包 https://dev.mysql.com/downloads/repo…

    数据库 2023年5月24日
    0143
  • pg数据库匹配正则

    select ‘41142619960609331x’ ~ ‘^[1-9]\d{5}\d{4}((0[1-9])|(10|11|12))(([0…

    数据库 2023年6月16日
    0139
  • Mybatis-Spring源码分析

    Mybatis-Spring 博主技术有限,本文难免有错误的地方,如果您发现了欢迎评论私信指出,谢谢JAVA技术交流群:737698533 当我们使用mybatis和spring整…

    数据库 2023年6月16日
    0130
  • vue入门(一)

    模板语法 插值语法 功能:用于解析标签体内容 写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性。 指令语法 功能:用于解析标签(包括:标签属性、标签内…

    数据库 2023年6月6日
    0109
  • DDD(Domain Driver Design)领域驱动模型

    Domain Primitive(DP) DP概念DP 是 DDD 中的一个基础概念,是 DDD 中可以执行的一个最小单元,最直接的体现是,将业务相关的参数定义在一个特定的领域中(…

    数据库 2023年6月6日
    0201
  • Linux 添加大于2TB磁盘扩容逻辑卷

    一、查看新添加磁盘以及分区情况 # lsblk 二、使用parted进行分区 # parted /dev/sdb // /dev/sdb 为上面查看到的新添加未分区的磁盘 (par…

    数据库 2023年6月11日
    0129
  • entitybuilder–一个简单的业务通用框架

    关于业务通用框架的思考 业务系统是千差万别的,例如,保存、更新和删除订单,或者保存订单和保存客户,走的根本不是一个流程。但是,它们还是有共同点,它们的流程大致可以分成下面的几个部分…

    数据库 2023年6月6日
    0125
  • Spring常见问题

    Spring常见问题 问渠那得清如许?为有源头活水来。 Spring 是个 java 企业级应用的开源开发框架。Spring 主要用来开发 Java 应用,但是有些扩展是针对构建 …

    数据库 2023年6月14日
    0112
  • 2021长安杯wp

    案件背景 2021年4月25日,上午8点左右,警方接到被害人金某报案,声称自己被敲诈数万元;经询问,昨日金某被嫌疑人诱导果聊,下载了某果聊软件,导致自己的通讯录和果聊视频被嫌疑人获…

    数据库 2023年6月11日
    0129
  • mysql安装及主从复制配置

    一、安装 mysql8.0 下载mysql 安装包http://mirrors.sohu.com/mysql/MySQL-8.0/ wget http://mirrors.sohu…

    数据库 2023年5月24日
    0101
  • 设计模式在业务系统中的应用

    本文的重点在于说明工作中所使用的设计模式,为了能够更好的理解设计模式,首先简单介绍一下业务场景。使用设计模式,可以简化代码、提高扩展性、可维护性和复用性。有哪些设计模式,这里就不再…

    数据库 2023年6月14日
    0131
  • MySQL事务提交流程

    有binlog的CR方式(重点核心!!):有binlog情况下,commit动作开始时,会有一个Redo XID 的动作记录写到redo,然后写data到binlog,binlog…

    数据库 2023年6月16日
    0121
  • Nginx基础入门篇(1)—优势及安装

    一、Nginx 的优势 1.1发展趋势: 2016年: 1.2、简介 Nginx (engine x) 是一个高性能的HTTP(解决C10k的问题)和反向代理服务器,也是一个IMA…

    数据库 2023年6月14日
    0106
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球