spring is embedded with cglib package, which hides a big pit

Posted by googlehunter on Wed, 26 Jan 2022 04:02:02 +0100

Problem discovery

At 9 a.m. on January 21, 2022, a large area of "system unknown error" was reported in the order system, resulting in some users unable to place orders normally. Query the background log and you can see a large number of duplicate class attempt s.

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)
	····Omit other stacks

problem analysis

First, through the stack, we can preliminarily judge that the error is caused by cglib trying to generate an existing class.

The code calls BeanMap.. Create (object) method, which generates a dynamic proxy class. We go directly to abstractclassgenerator From the source code of create (object), we can see that if it is already in the global cache, it will not be generated again. Logically, the proxy class will not be generated repeatedly. Is the cache invalid?

At first I suspected that the cache was disabled. However, this useCache field can only be passed through abstractclassgenerator Setusecache (Boolean) method is set, and this method is not referenced anywhere in the whole project. Therefore, this assumption is not tenable.

abstract public class AbstractClassGenerator<T> implements ClassGenerator {
    // Use cache
    private boolean useCache = true;
    private static final Object NAME_KEY = new Object();
    // The cache is global
    private static final Source SOURCE = new Source(BeanMap.class.getName());
    protected static class Source {
        String name;
        /*
         * Global cache, format:
         * <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 9:51:41 am, January 24
         * @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);
        }
    }
}

It suddenly occurred to me that this cglib is just a spring embedded cglib, not a "real cglib". The cache only takes effect in the current cglib. If the native cglib also wants to create this class, will it report an error?

By looking at the references, it is true that this situation exists in the project: different cglib s are used to create the same class:

Then I experimented with the code, and the same error was reported:

Problem solving

Therefore, we can conclude that if spring and native cglib are used to generate the same class, the duplicate class attempt error will appear because the cache cannot be shared.

Knowing the reason, the solution is very simple. Just change the guide package of cglib to the same one.

After the repair, there is no such error report in production, which basically proves that we are right.

epilogue

The above is the processing process of this production error report. Here I have some doubts:

  1. cglib determines whether a class exists. Why not directly check the class in the project? Using cache as an unreliable means?
  2. Why doesn't spring rely directly on cglib? Instead of embedding one yourself?

Finally, thank you for reading and welcome communication and correction.

This article is an original article. Please attach the original source link for Reprint: https://www.cnblogs.com/ZhangZiSheng001/p/15838657.html

Topics: Java Spring bug