[Javassist]「javassist.CannotCompileException: cannot find ~"」の解決方法

2021/01/30に公開

はじめに

 Javassist の CtBehavior#insertBefore メソッドを利用した際に javassist.CannotCompileException が発生したので、その解決方法をメモ。

 具体的には以下のエラーメッセージ
javassist.CannotCompileException: cannot find okhttp3.Request

コード

 エラーが発生したJavaコードは以下の通りです。
okhttp3/internal/http/RealInterceptorChain#proceed メソッドに前処理を追加してます。

package org.example.agent05;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.io.ByteArrayInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class MyAgent {

  public static void premain(String agentArg, Instrumentation inst) {
    inst.addTransformer(new ClassFileTransformer() {
      @Override
      public byte[] transform(ClassLoader loader,
                  String className,
                  Class<?> classBeingRedefined,
                  ProtectionDomain protectionDomain,
                  byte[] classfileBuffer) throws IllegalClassFormatException {
        if ( className.equals("okhttp3/internal/http/RealInterceptorChain") ) {
          try {
            ClassPool classPool = new ClassPool();
            CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
            CtMethod ctMethod = ctClass.getDeclaredMethod("proceed");
            ctMethod.insertBefore("System.out.println(\"Hook proceed method.\");");
            return ctClass.toBytecode();
          } catch ( Exception ex ) {
            ex.printStackTrace();
            return new byte[0];
          }
        } else {
          return new byte[0];
        }
      }
    });
  }

}

エラーメッセージ

 「okhttp3.Request」が見つからないとのこと。

javassist.CannotCompileException: cannot find okhttp3.Request
	at javassist.CtBehavior.insertBefore(CtBehavior.java:803)
	at javassist.CtBehavior.insertBefore(CtBehavior.java:766)
	at org.example.agent05.MyAgent$1.transform(MyAgent.java:28)
	at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
	at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:428)
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:756)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
	at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:151)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
	at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:223)
	at okhttp3.RealCall.execute(RealCall.java:81)
	at com.example.demo.controllers.api.ApiDemoController.ssrf1(ApiDemoController.java:83)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1061)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:961)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:626)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:888)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1597)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)
Caused by: javassist.NotFoundException: okhttp3.Request
	at javassist.ClassPool.get(ClassPool.java:422)
	at javassist.bytecode.Descriptor.toCtClass(Descriptor.java:571)
	at javassist.bytecode.Descriptor.getParameterTypes(Descriptor.java:424)
	at javassist.CtBehavior.getParameterTypes(CtBehavior.java:323)
	at javassist.CtBehavior.insertBefore(CtBehavior.java:781)
	... 69 more

解決方法

方法①

ClassPool#appendClassPathメソッドを使う。

appendClassPath(ClassPath cp)
Appends a ClassPath object to the end of the search path.

ClassPool classPool = new ClassPool();
classPool.appendClassPath(new LoaderClassPath(loader)); // 追加

方法②

ClassPool#insertClassPath メソッドを使う。

insertClassPath(java.lang.String pathname)
Inserts a directory or a jar (or zip) file at the head of the search path.

ClassPool classPool = new ClassPool();
classPool.insertClassPath("~/.gradle/caches/modules-2/files-2.1/com.squareup.okhttp3/okhttp/3.14.9/3e6d101343c7ea687cd593e4990f73b25c878383/okhttp-3.14.9.jar"); // 追加

そして翌日...。同様のエラーが発生...。そして解決

エラーメッセージ

 次は、「java.nio.file.Path」が見つからないとのこと。

javassist.CannotCompileException: cannot find java.nio.file.Path
	at javassist.CtBehavior.insertBefore(CtBehavior.java:803)
(以下略)

解決方法

ClassPool#appendSystemPath を追加したら解決しました。

appendSystemPath()
Appends the system search path to the end of the search path.

ClassPool classPool = new ClassPool();
classPool.appendSystemPath(); // 追加
classPool.appendClassPath(new LoaderClassPath(loader));

参考

Discussion