问题发现

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

  1. java.lang.LinkageError-->loader (instance of org/springframework/boot/loader/LaunchedURLClassLoader): attempted duplicate class definition for name: "com/order/vo/OrderAndExtendVO$$BeanMapByCGLIB$$e8178b2a"
  2. StackTrace:
  3. 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"
  4. at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:538)
  5. at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
  6. at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:110)
  7. at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:108)
  8. at org.springframework.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54)
  9. at java.util.concurrent.FutureTask.run(FutureTask.java:266)
  10. at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61)
  11. at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34)
  12. at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:134)
  13. at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319)
  14. at org.springframework.cglib.beans.BeanMap$Generator.create(BeanMap.java:127)
  15. at org.springframework.cglib.beans.BeanMap.create(BeanMap.java:59)
  16. ····省略其他堆栈

问题分析

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

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

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

  1. abstract public class AbstractClassGenerator<T> implements ClassGenerator {
  2. // 是否使用缓存
  3. private boolean useCache = true;
  4. private static final Object NAME_KEY = new Object();
  5. // 缓存是全局的
  6. private static final Source SOURCE = new Source(BeanMap.class.getName());
  7. protected static class Source {
  8. String name;
  9. /*
  10. * 全局缓存,格式为:
  11. * <blockquote><pre>
  12. * {
  13. * "classLoader1":{
  14. * NAME_KEY:["className1","className2"],
  15. * "className1":class1,
  16. * "className2":class2
  17. * },
  18. * "classLoader2":{
  19. * NAME_KEY:["className2","className3"],
  20. * "className3":class3,
  21. * "className2":class2
  22. * }
  23. * }
  24. * zzs001
  25. * </pre></blockquote>
  26. * @author zzs
  27. * @date 2022年1月24日 上午9:51:41
  28. * @param args void
  29. */
  30. Map cache = new WeakHashMap();
  31. public Source(String name) {
  32. this.name = name;
  33. }
  34. }
  35. protected Object create(Object key) {
  36. try {
  37. Class gen = null;
  38. synchronized (source) {
  39. ClassLoader loader = getClassLoader();
  40. Map cache2 = null;
  41. cache2 = (Map)source.cache.get(loader);
  42. if (cache2 == null) {
  43. cache2 = new HashMap();
  44. cache2.put(NAME_KEY, new HashSet());
  45. source.cache.put(loader, cache2);
  46. } else if (useCache) {
  47. Reference ref = (Reference)cache2.get(key);
  48. gen = (Class) (( ref == null ) ? null : ref.get());
  49. }
  50. if (gen == null) {
  51. Object save = CURRENT.get();
  52. CURRENT.set(this);
  53. try {
  54. this.key = key;
  55. if (attemptLoad) {
  56. try {
  57. gen = loader.loadClass(getClassName());
  58. } catch (ClassNotFoundException e) {
  59. // ignore
  60. }
  61. }
  62. if (gen == null) {
  63. byte[] b = strategy.generate(this);
  64. String className = ClassNameReader.getClassName(new ClassReader(b));
  65. getClassNameCache(loader).add(className);
  66. gen = ReflectUtils.defineClass(className, b, loader);
  67. }
  68. if (useCache) {
  69. cache2.put(key, new WeakReference(gen));
  70. }
  71. return firstInstance(gen);
  72. } finally {
  73. CURRENT.set(save);
  74. }
  75. }
  76. }
  77. return firstInstance(gen);
  78. } catch (RuntimeException e) {
  79. throw e;
  80. } catch (Error e) {
  81. throw e;
  82. } catch (Exception e) {
  83. throw new CodeGenerationException(e);
  84. }
  85. }
  86. }

我突然想到,这个 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. SpringBoot整合mybatis-plus并实现代码自动生成

    1.引入maven <dependency> <groupId>org.springframework.boot</groupId> <artifactId& ...

  2. 【LeetCode】1631. 最小体力消耗路径 Path With Minimum Effort

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 解题思路 并查集 代码 刷题心得 欢迎加入组织 日期 题目地址:https ...

  3. 【LeetCode】833. Find And Replace in String 解题报告(Python)

    [LeetCode]833. Find And Replace in String 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu ...

  4. DP? (hdu3944)

    DP? Time Limit: 10000/3000 MS (Java/Others)    Memory Limit: 128000/128000 K (Java/Others)Total Subm ...

  5. Nginx 常用配置清单

    侦听端口: server {# Standard HTTP Protocollisten 80;# Standard HTTPS Protocollisten 443 ssl;# For http2l ...

  6. 用户线程&&守护线程

         守护线程是为用户线程服务的,当一个程序中的所有用户线程都执行完成之后程序就会结束运行,程序结束运行时不会管守护线程是否正在运行,由此我们可以看出守护线程在 Java 体系中权重是比较低的.当 ...

  7. 【Java例题】4.2 级数求和2

    2. 计算级数之和: y=1/1!*x-1/3!*x^3+1/5!*x^5+...+ (-1)^n/(2n+1)!*x^(2n+1). 这里的"^"表示乘方,"!&quo ...

  8. fork之后,子进程从父进程那继承了什么(转载)

    转载自:https://blog.csdn.net/xiaojun111111/article/details/51764389 知道子进程自父进程继承什么或未继承什么将有助于我们.下面这个名单会因为 ...

  9. uniapp使用uni.openDocument打开文件时,安卓打开成功,iOS打开失败【原因:打开的文件的文件名是中文】

    解决办法:使用escape进行文件名编码 uni.downloadFile({ url: url, success: function(res) { var filePath = res.tempFi ...

  10. Java初学者作业——编写JAVA程序,在控制台输入一位学生的英语考试成绩,根据评测规则,输出对应的成绩等级。定义方法实现学生成绩的评测功能。

    返回本章节 返回作业目录 需求说明: 编写JAVA程序,在控制台输入一位学生的英语考试成绩,根据评测规则,输出对应的成绩等级.要求:定义方法实现学生成绩的评测功能. 学生的英语考试成绩进行评测,评测规 ...