问题发现

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. vue使用npm install安装太慢连接不上(设置使用淘宝镜像)

    当使用默认的npm install速度太慢时候,可以配置使用淘宝镜像 npm config set registry https://registry.npm.taobao.org

  2. c++之一个方便的日志库

    概述 本文演示环境: win10 + vs2017 日志,我用的很少,通常是用作动态库调试使用. 日志记录下来,基本就没看过,除非模块出现了问题. 使用cmake管理的项目 使用C++封装了C语言读写 ...

  3. E. Congruence Equation

    E. Congruence Equation 思路: 中国剩余定理 \(a^n(modp) = a^{nmod(p-1)}(modp)\),那么枚举在\([0,n-2]\)枚举指数 求\(a^i\)关 ...

  4. light oj -1245 - Harmonic Number (II)

    先举个例子,假如给你的数是100的话,将100/2=50;是不是就是100除100-51之间的数取整为1: 100/3=33;100除50到34之间的数为2,那么这样下去到sqrt(100);就可以求 ...

  5. 菜鸟物流的运输网络(计蒜客复赛F)

    菜鸟物流有自己的运输网络,网络中包含 nn 个城市物流集散中心,和 mm 对城市之间的运输线路(线路是双向的).菜鸟物流允许淘宝卖家自行确定包裹的运输路径,但只有一条限制规则:不允许经过重复的城市.淘 ...

  6. 1382 - The Queue

    1382 - The Queue   PDF (English) Statistics Forum Time Limit: 2 second(s) Memory Limit: 32 MB On som ...

  7. Attention Is All You Need

    目录 概 主要内容 Positional Encoding auto_regressive 额外的细节 代码 Vaswani A., Shazeer N., Parmar N., Uszkoreit ...

  8. MySQL数据库安装Version5.7

    MySQL数据库版本: mysql-5.7.22-linux-glibc2.12-x86_64 Linux服务器系统: CentOS 7.4 64bit MySQL安装用户: mysql/aliyun ...

  9. Ranger知识地图

    自己总结的Ranger的学习路线和知识点, 提供给感兴趣的同学入门参考之用. Ranger入门路线 1.准备能够上网的电脑,Eclisp开发环境,Linux服务器等: 2.掌握Hadoop(HDFS/ ...

  10. 使用.NET 6开发TodoList应用(21)——实现API版本控制

    系列导航及源代码 使用.NET 6开发TodoList应用文章索引 需求 API接口版本管理,对于一些规模稍大的企业应用来说,是经常需要关注的一大需求.尽管我们的示例程序TodoList很简单,但是我 ...