问题发现

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<T> 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;
/*
* 全局缓存,格式为:
* <blockquote><pre>
* {
* "classLoader1":{
* NAME_KEY:["className1","className2"],
* "className1":class1,
* "className2":class2
* },
* "classLoader2":{
* NAME_KEY:["className2","className3"],
* "className3":class3,
* "className2":class2
* }
* }
* zzs001
* </pre></blockquote>
* @author zzs
* @date 2022年1月24日 上午9:51:41
* @param args void
*/
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 来生成同一个 class,会因为缓存无法共享而出现 duplicate class attempt 的报错

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

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

结语

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

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

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

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

spring内嵌cglib包,这里藏着一个大坑的更多相关文章

  1. spring内嵌jetty容器,实现main方法启动web项目

    Jetty 是一个开源的servlet容器,它为基于Java的web容器,例如JSP和servlet提供运行环境.Jetty是使用Java语言编写的,它的API以一组JAR包的形式发布.开发人员可以将 ...

  2. 学习Tomcat(七)之Spring内嵌Tomcat

    前面的文章中,我们介绍了Tomcat容器的关键组件和类加载器,但是现在的J2EE开发中更多的是使用SpringBoot内嵌的Tomcat容器,而不是单独安装Tomcat应用.那么Spring是怎么和T ...

  3. Java IO:如何得到Jar包中内嵌Jar包的时间戳

    ClassLoader bladeClassLoader = BladeCLI.class.getClassLoader(); URL url = bladeClassLoader.getResour ...

  4. spring 5.x 系列第20篇 ——spring简单邮件、附件邮件、内嵌资源邮件、模板邮件发送 (代码配置方式)

    源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 一.说明 1.1 项目结构说明 邮件发送配置类为com.heibaiyin ...

  5. spring 5.x 系列第19篇 ——spring简单邮件、附件邮件、内嵌资源邮件、模板邮件发送 (xml配置方式)

    源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 一.说明 1.1 项目结构说明 邮件发送配置文件为springApplic ...

  6. Razor Page Library:开发独立通用RPL(内嵌wwwroot资源文件夹)

    ASP.NET Core知多少系列:总体介绍及目录 Demo路径:GitHub-RPL.Demo 1. Introduction Razor Page Library 是ASP.NET Core 2. ...

  7. Jetty 开发指南:Jetty 内嵌开发

    Jetty的口号是“不要在Jetty中部署你的应用程序,在你的应用程序中部署Jetty!” 这意味着,作为将应用程序捆绑为要部署在Jetty中的标准WAR的替代方案,Jetty旨在成为一个软件组件,可 ...

  8. Springboot源码分析之代理对象内嵌调用

    摘要: 关于这个话题可能最多的是@Async和@Transactional一起混用,我先解释一下什么是代理对象内嵌调用,指的是一个代理方法调用了同类的另一个代理方法.首先在这儿我要声明事务直接的嵌套调 ...

  9. H2内嵌数据库的使用

    H2内嵌数据库的使用 H2是一个开源的嵌入式数据库引擎,采用java语言编写,不受平台的限制. 同时H2提供了一个十分方便的web控制台用于操作和管理数据库内容. H2还提供兼容模式,可以兼容一些主流 ...

随机推荐

  1. 【LeetCode】1399. 统计最大组的数目 Count Largest Group

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 直接求 日期 题目地址:https://leetcod ...

  2. 【LeetCode】873. Length of Longest Fibonacci Subsequence 解题报告(Python)

    [LeetCode]873. Length of Longest Fibonacci Subsequence 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: ...

  3. 【LeetCode】309. Best Time to Buy and Sell Stock with Cooldown 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 动态规划 日期 题目地址:https://leetc ...

  4. 比赛难度(HDU4546)

    比赛难度 Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)Total Submis ...

  5. DEFENSE-GAN: PROTECTING CLASSIFIERS AGAINST ADVERSARIAL ATTACKS USING GENERATIVE MODELS

    目录 概 主要内容 Samangouei P, Kabkab M, Chellappa R, et al. Defense-GAN: Protecting Classifiers Against Ad ...

  6. 微信小程序-自定义菜单导航(实现楼梯效果)

    设计初衷 在开发页面时,往往需要实现,点击页面的导航菜单页面滚动到相应位置,滚动页面实现菜单选项的高亮.在html开发中,我们可以用到a标签锚点实现,jq的动画相结合实现类似效果.在框架中vant U ...

  7. MySQL8.0.20安装详解

    https://blog.csdn.net/yeb112233/article/details/106042867/ alter user root@localhost identified by ' ...

  8. python all用法记录

    all函数可以返回对象中是否所有元素均为True 代码如下:(此处仅做简单记录,日后不忙时再认真总结) >>> print(list(map(lambda x: True if x ...

  9. vue - 指令创建 vue工程

    1.在需要创建工程的文件夹里打开cmd 执行 vue -V 看看版本号是否正常, 创建工程 vue create [工程名称] 如:vue create mytestvue 然后会弹出选择 按方向键, ...

  10. js 对 date 和 字符串 类型的正确互换【各浏览器兼容】,解决invalid Date

    1.前言 有个需求,想要把指定日期时间的字符串转换成date类型 pc浏览器正常转换,但手机浏览器 返回结果是 invalid Date [无效的日期] 2.原因 出现这样不兼容的原因其实很简单, p ...