使用Java Agent+Javassist增强Spring应用时找不到HttpServletRequest接口
我使用Java Agent结合Javassist为Spring应用添加插桩代码,尝试在DispatcherServlet的doDispatch()方法前后插入System.out输出语句,却遇到异常:
javassist.CannotCompileException: cannot find javax.servlet.http.HttpServletRequest
javax.servlet.http.HttpServletRequest是DispatcherServlet已导入的接口类,推测是类加载顺序导致的问题。我尝试通过pool.appendClassPath("javax/servlet/http")添加类路径,但并未解决问题。我的Transformer代码如下:
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { boolean changed = false; if (className.equals("org/springframework/web/servlet/DispatcherServlet")) { try { String name = className.replaceAll("/", "."); pool.insertClassPath(new ByteArrayClassPath(name, classfileBuffer)); CtClass cclass = pool.get(name); for (CtMethod currentMethod: cclass.getDeclaredMethods("doDispatch")) { try { currentMethod.insertBefore("System.out.println(\"before handler\");"); currentMethod.insertAfter("System.out.println(\"after handler\");"); changed = true; } catch(javassist.CannotCompileException e) { logger.error(e + ": " + currentMethod); } } if (changed) { classfileBuffer = cclass.toBytecode(); cclass.detach(); return classfileBuffer; } cclass.detach(); } catch(NotFoundException e) { logger.error(e + ": " + className); } catch (Exception e) { logger.error(e + ": " + className); } } return null; // didn't change anything }
关联目标类的类加载器到Javassist的ClassPool
Javassist默认使用自身的类加载器,而DispatcherServlet和HttpServletRequest是由应用类加载器加载的。需要将目标类的ClassLoader添加到ClassPool的类路径中:// 在插入ByteArrayClassPath之后添加这行 pool.insertClassPath(new LoaderClassPath(loader));这样Javassist就能通过应用类加载器正确找到
HttpServletRequest类。移除无效的类路径添加代码
之前使用的pool.appendClassPath("javax/servlet/http")是错误用法,该方法需要传入JAR文件路径或目录路径,而非类的包路径,直接删除这行代码即可。应急方案:使用全限定类名(非必要)
如果类加载器关联后仍有问题(尽管你的插桩代码本身没用到HttpServletRequest),可以在涉及该类的代码中使用全限定类名,例如:javax.servlet.http.HttpServletRequest request = ...。但核心解决方式还是类加载器的关联。
内容的提问来源于stack exchange,提问作者Ido Kelson

