GitHub user zhanjinhao added a comment to the discussion: Using Custom ClassLoader to Isolate Log42j and Slf4j. Console log: Properties contains an invalid element or attribute "property"
@vy thanks. @jhl221123 I think I've found the real reason. Not about findResource()/findResources(). **It is about findLoadedClass().** ### Problem Analysis I have compared my implementation with yours. And adjusting my implementation to yours step by step . I found if I don't introduce findLoadedClass() when overriding loadClass(), the directory will not be created correctly. <img width="2189" height="818" alt="image" src="https://github.com/user-attachments/assets/25c9857a-9d23-4b59-bad5-aad5cf8edd90" /> If I don't extend URLClassLoader. And I still load class and resources in my way except including a check for already-loaded classes. the directory will be created correctly. ``` /** * It will use commons-io package, so I package my project to a fat jar. **/ package cn.addenda.loglearn; import lombok.Getter; import org.apache.commons.io.IOUtils; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; public class LogClassLoader6 extends ClassLoader { @Getter private static LogClassLoader6 DEFAULT_LOADER; private final List<String> logPrefixList = new ArrayList<>(); List<Jar> jars; static { ClassLoader.registerAsParallelCapable(); } public LogClassLoader6(ClassLoader parent) { super(parent); jars = doGetJars(); logPrefixList.add("org.slf4j."); logPrefixList.add("org.apache.logging.log4j."); logPrefixList.add("org.apache.logging.slf4j."); } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> loadedClass = findLoadedClass(name); if (loadedClass != null) { return loadedClass; } boolean shouldIsolate = false; for (String prefix : logPrefixList) { if (name.startsWith(prefix)) { shouldIsolate = true; break; } } if (shouldIsolate) { loadedClass = doFindClass(name); if (resolve) { resolveClass(loadedClass); } return loadedClass; } return super.loadClass(name, resolve); } } @Override public Enumeration<URL> getResources(String name) throws IOException { List<URL> allResources = new ArrayList<>(); for (Jar jar : jars) { JarEntry jarEntry = jar.jarFile.getJarEntry(name); if (jarEntry == null) { continue; } try { URL url = new URL("jar:file:" + jar.sourceFile.getAbsolutePath() + "!/" + name); allResources.add(url); } catch (Exception e) { System.out.println(String.format("find file {} error", name)); e.printStackTrace(); } } Iterator<URL> iterator = allResources.iterator(); return new Enumeration<URL>() { @Override public boolean hasMoreElements() { return iterator.hasNext(); } @Override public URL nextElement() { return iterator.next(); } }; } @Override public URL getResource(String name) { // todo 设置为线程上下文类加载器,再从这里读取外部目录的log4j2.xml for (Jar jar : jars) { JarEntry jarEntry = jar.jarFile.getJarEntry(name); if (jarEntry == null) { continue; } try { // 返回第一个 URL url = new URL("jar:file:" + jar.sourceFile.getAbsolutePath() + "!/" + name); return url; } catch (Exception e) { System.out.println(String.format("find file {} error", name)); e.printStackTrace(); } } return null; } public static synchronized void initDefaultLoader() { if (DEFAULT_LOADER == null) { // ClassLoader extensionClassLoader = ClassLoader.getSystemClassLoader().getParent(); ClassLoader extensionClassLoader = ClassLoader.getSystemClassLoader(); DEFAULT_LOADER = new LogClassLoader6(extensionClassLoader); } } private Class<?> doFindClass(String name) throws ClassNotFoundException { List<Jar> _allJarList = jars; String concat = name.replace(".", "/").concat(".class"); for (Jar jar : _allJarList) { JarEntry jarEntry = jar.jarFile.getJarEntry(concat); if (jarEntry == null) { continue; } try { URL url = new URL("jar:file:" + jar.sourceFile.getAbsolutePath() + "!/" + concat); byte[] byteArray = IOUtils.toByteArray(url); return defineClass(name, byteArray, 0, byteArray.length); } catch (Exception e) { System.out.println(String.format("find class %s error", name)); } } throw new ClassNotFoundException("can not find " + name); } private List<Jar> doGetJars() { List<Jar> _allJarList = new ArrayList<>(); File agentJarDir = AgentPackagePath.getPath(); System.out.println("Agent base directory: " + agentJarDir); File logLibDir = new File(new File(agentJarDir, "lib"), "log"); if (logLibDir.exists() && logLibDir.isDirectory()) { String[] list = logLibDir.list((dir, name) -> name.endsWith(".jar")); if (list == null || list.length == 0) { return _allJarList; } for (String s : list) { File jarSourceFile = new File(logLibDir, s); try { Jar jar = new Jar(new JarFile(jarSourceFile), jarSourceFile); _allJarList.add(jar); System.out.println(String.format("load jar %s success.", jarSourceFile)); } catch (Exception e) { System.out.println(String.format("jar %s load fail.", jarSourceFile)); e.printStackTrace(); } } } return _allJarList; } private static class Jar { /** * jar文件对对应的jarFile对象 */ private final JarFile jarFile; /** * jar文件 */ private final File sourceFile; public Jar(JarFile jarFile, File sourceFile) { this.jarFile = jarFile; this.sourceFile = sourceFile; } } } ``` In my production project, I will extent URLClassLoader because I am indeed not clear about operating jar. ### Different Class With my previous LogClassLoader, the codebelow will output : loggerContextClass != loggerContextClass2. ``` loggerFactoryClass = Class.forName("org.slf4j.LoggerFactory", true, defaultLoader); loggerFactoryClass2 = Class.forName("org.slf4j.LoggerFactory", true, defaultLoader); if (loggerContextClass == loggerFactoryClass2) { System.out.println("loggerContextClass == loggerContextClass2"); } else { System.out.println("loggerContextClass != loggerContextClass2"); } ``` **A class‘s binary is loaded from the jar multiple times, each time the class is different.** ### Conclusion So I think the real reason is that I don't check the already-loaded classes However, as for how it affects log4j2, it's too difficult for me to analyze. Log4j2 loaded too many classes at startup. GitHub link: https://github.com/apache/logging-log4j2/discussions/3960#discussioncomment-14771588 ---- This is an automatically sent email for [email protected]. To unsubscribe, please send an email to: [email protected]
