找回密码
 立即注册
首页 业界区 业界 JAVA找出哪个类import了不存在的类

JAVA找出哪个类import了不存在的类

杓疠? 前天 02:45
JAVA找出哪个类import了不存在的类

1. 背景

在JAVA中一个类A,import 另外的一个类B.然后在容器启动时,只会提示B类不存在,不会出现任何A类相关的信息
Tomcat中错误信息如下,测试代码使用org.slf4j.Logger说明 ,部分错误信息如下
  1. at java.lang.Thread.run(Thread.java:748)
  2. Caused by: java.lang.NoClassDefFoundError: Lorg/slf4j/Logger;
  3. at java.lang.Class.getDeclaredFields0(Native Method)
  4. at java.lang.Class.privateGetDeclaredFields(Class.java:2583)
  5. ... 10 more
  6. Caused by: java.lang.ClassNotFoundException: org.slf4j.Logger
  7. at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1360)
  8. at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1182)
  9. ... 23 more
复制代码
2. 日志能解决吗

在容器TOMCAT为类WebappClassLoaderBase开启详细日志,在/conf/logging.properties 添加如下日志
  1. org.apache.catalina.loader.WebappClassLoaderBase.level = FINE
复制代码
加上日志以后,输出内容如下
  1. org.apache.catalina.loader.WebappClassLoaderBase.loadClass   Loading class from local repository
  2. org.apache.catalina.loader.WebappClassLoaderBase.loadClass loadClass(dxxs.BTestClass, false)
  3. org.apache.catalina.loader.WebappClassLoaderBase.loadClass   Searching local repositories
  4. org.apache.catalina.loader.WebappClassLoaderBase.findClass     findClass(dxxs.BTestClass)
  5. org.apache.catalina.loader.WebappClassLoaderBase.loadClass   Loading class from local repository
  6. org.apache.catalina.loader.WebappClassLoaderBase.loadClass loadClass(org.slf4j.Logger, false)
  7. org.apache.catalina.loader.WebappClassLoaderBase.loadClass   Searching local repositories
  8. org.apache.catalina.loader.WebappClassLoaderBase.findClass     findClass(org.slf4j.Logger)
  9. org.apache.catalina.loader.WebappClassLoaderBase.findClass     --> Returning ClassNotFoundException
复制代码
从日志输出上来看,只是知道 dxxs.BTestClass 后就加载 org.slf4j.Logger,但并不能确定就是 dxxs.BTestClass 导致的
3. 分析字节码能解决吗

通过日志分析,感觉上是可以,但是不能确定.所以为了确定,采用 Java Agent 方式分析,当前加载的class是否引用了当前环境不存在的class
在TOMCAT启动时,配置参数,在启动时,注入编写jar
  1. set JAVA_OPTS=%JAVA_OPTS% -javaagent:"%CATALINA_HOME%\lib\class-load-monitor-agent.jar"="%CATALINA_HOME%\conf\agent-config.properties"
复制代码
再次启动后,对应输出内容如下
  1. [ClassLoadMonitor] ==========================================
  2. [ClassLoadMonitor] FOUND REFERENCE TO MISSING CLASS:
  3. [ClassLoadMonitor] Source class: dxxs.BTestClass
  4. [ClassLoadMonitor] Missing class: org.slf4j.Logger
  5. [ClassLoadMonitor] ClassLoader: ParallelWebappClassLoader
  6.   context: test
  7.   delegate: false
  8. ----------> Parent Classloader:
  9. java.net.URLClassLoader@87aac27
  10. [ClassLoadMonitor] Thread: localhost-startStop-1
复制代码
这次从日志中,可以明确知道 dxxs.BTestClass 引用了类org.slf4j.Logger,而当前环境中却不存在 org.slf4j.Logger.由此问题解决
4. 如何实现

构建配置,实现javaagent,需要在MANIFEST.MF文件中配置Premain-Class和Agent-Class,所有当前pom.xml构建配置信息如下
  1. <plugin>
  2.     <groupId>org.apache.maven.plugins</groupId>
  3.     maven-jar-plugin</artifactId>
  4.     <version>3.2.0</version>
  5.     <configuration>
  6.         
  7.             <manifestEntries>
  8.                 <Premain-Class>com.example.ClassLoadMonitorAgent</Premain-Class>
  9.                 com.example.ClassLoadMonitorAgent</Agent-Class>
  10.                 <Can-Redefine-Classes>true</Can-Redefine-Classes>
  11.                 <Can-Retransform-Classes>true</Can-Retransform-Classes>
  12.             </manifestEntries>
  13.         </archive>
  14.     </configuration>
  15. </plugin>
复制代码
这个代码中实现两个方法
  1. public static void premain(String agentArgs, Instrumentation inst);
  2. public static void agentmain(String agentArgs, Instrumentation inst)
复制代码
添加类加载转换器
  1. inst.addTransformer(new ClassFileTransformer() {
  2. @Override
  3.   public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
  4.   ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
  5.       if (className != null && classfileBuffer != null) {
  6.           String fullClassName = className.replace('/', '.');
  7.           if (!checkedClasses.contains(fullClassName)) {
  8.               checkedClasses.add(fullClassName);            
  9.               analyzeBytecode(fullClassName, classfileBuffer, loader);
  10.           }
  11.       }
  12.       return classfileBuffer;
  13.   }
  14. }, true);
复制代码
核心分析类分析代码 analyzeBytecode
关于常量池信息参考,可以查看地址https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-4.html#jvms-4.4
  1. private static void analyzeBytecode(String className, byte[] bytecode, ClassLoader loader) {
  2.     try {
  3.         DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytecode));
  4.         int magic = dis.readInt();
  5.         if (magic != 0xCAFEBABE) {
  6.             return;
  7.         }
  8.         dis.readShort();
  9.         dis.readShort();
  10.         int constantPoolCount = dis.readShort();
  11.         String[] utf8Pool = new String[constantPoolCount];
  12.         int[] classRefIndices = new int[constantPoolCount];
  13.         int classRefCount = 0;
  14.         for (int i = 1; i < constantPoolCount; i++) {
  15.             byte tag = dis.readByte();
  16.             switch (tag) {
  17.                 case 1: // UTF8
  18.                     utf8Pool[i] = dis.readUTF();
  19.                     break;
  20.                 case 7: // Class
  21.                     int nameIndex = dis.readShort();
  22.                     classRefIndices[classRefCount++] = nameIndex;
  23.                     break;
  24.                 case 8: // String
  25.                     dis.readShort();
  26.                     break;
  27.                 case 9:  // Fieldref
  28.                 case 10: // Methodref
  29.                 case 11: // InterfaceMethodref
  30.                 case 12: // NameAndType
  31.                     dis.readShort();
  32.                     dis.readShort();
  33.                     break;
  34.                 case 3: // Integer
  35.                 case 4: // Float
  36.                     dis.readInt();
  37.                     break;
  38.                 case 5: // Long
  39.                 case 6: // Double
  40.                     dis.readLong();
  41.                     i++;
  42.                     break;
  43.                 case 15: // MethodHandle
  44.                     dis.readByte();
  45.                     dis.readShort();
  46.                     break;
  47.                 case 16: // MethodType
  48.                     dis.readShort();
  49.                     break;
  50.                 case 18: // InvokeDynamic
  51.                     dis.readShort();
  52.                     dis.readShort();
  53.                     break;
  54.                 default:
  55.                     break;
  56.             }
  57.         }
  58.         for (int i = 0; i < classRefCount; i++) {
  59.             int utf8Index = classRefIndices[i];
  60.             if (utf8Index > 0 && utf8Index < constantPoolCount && utf8Pool[utf8Index] != null) {
  61.                 // 小游戏 地心侠士
  62.                 // 获取到当前class文件中引用类名
  63.                 String referencedClass = utf8Pool[utf8Index].replace('/', '.');
  64.                 // className 配置是要检查的的类名
  65.                 checkReference(className, referencedClass, loader);
  66.             }
  67.         }
  68.         // 小游戏 地心侠士
  69.         // 检查class文件,其他地方是否包含丢失的class
  70.         scanForClassReferences(className, bytecode, loader);
  71.     } catch (Exception e) {
  72.         
  73.     }
  74. }
  75. private static void scanForClassReferences(String className, byte[] bytecode, ClassLoader loader) {
  76.     // 公众号: 小满小慢
  77.     for (String missingClass : missingClasses) {
  78.         String internalName = missingClass.replace('.', '/');
  79.         String descriptorFormat = "L" + internalName + ";";
  80.         if (containsClassReference(bytecode, internalName) ||
  81.             containsClassReference(bytecode, descriptorFormat)) {
  82.             logClassReference(className, missingClass, loader);
  83.         }
  84.     }
  85. }
复制代码
如何使用?


  • 生成配置文件 agent-config.properties
    配置内容如下:
    1. # 小游戏 地心侠士
    2. # 公众号 小满小漫 精心制作
    3. # 配置需要监控的不存在的类,多个类用逗号或分号分隔
    4. missingClasses=org.slf4j.Logger
    复制代码
  • 修改启动参数
    在catalina.bat文件中添加,agent配置信息,修改内容如下
    1. set JAVA_OPTS=%JAVA_OPTS% -javaagent:"%CATALINA_HOME%\lib\class-load-monitor-agent-1.0-SNAPSHOT.jar"="%CATALINA_HOME%\lib\agent-config.properties"
    复制代码
总结

需要找缺失类有那个类导致的情况不多见.但是要查找,确实很费时间.在十多年JAVA斗智斗勇的过程中,这也是头一次遇到.以往提示找不到某个class,找到这个class对应jar往对应的环境一放就可以.主要这次是排查一个历史项目代码,当时日志文件直接使用log4j.后来项目升级了,而客户环境class没有升级.导致在客户客开代码在开发环境完全没问题,切换客户环境就报错奇怪的现象,class-load-monitor-agent-1.0-SNAPSHOT.jar,如果需要,请关注公众号[小满小慢]回复javanotfound获取下载地址
原文地址: https://mp.weixin.qq.com/s/YCbyaj4tPzjc22JJ5317VQ

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册