簡體   English   中英

Java-“攔截”私有方法

[英]Java - “intercept” a private method

我知道這已經被問過了,答案通常是“您不能”和/或“不要”,但是無論如何我都在嘗試。

上下文是我正在嘗試設置一些“黑魔法”來輔助測試。 我的代碼最終在JUnit下運行,並且系統的本質是,盡管我可以訪問大多數我想要的庫(ByteBuddy,Javassist等),但是在運行該代碼之前我無法使用它,我一直在忙着上課。

這是設置:

// External Library that I have no control over:
package com.external.stuff;

/** This is the thing I ultimately want to capture a specific instance of. */
public class Target {...}

public interface IFace {
  void someMethod();
}

class IFaceImpl {
  @Override  
  void someMethod() {
     ...
     Target t = getTarget(...);
     doSomethingWithTarget(t);
     ...
  }

  private Target getTarget() {...}
  private void doSomethingWithTarget(Target t) {...}
}

在測試不可思議性方面,我有一個IFace實例,我碰巧知道它是一個IFaceImpl。 想要是能偷的實例Target內部產生。 實際上,這將具有與以下相同的效果(如果私有方法是可重寫的):

class MyIFaceImpl extends IFaceImpl{
  private Consumer<Target> targetStealer;

  @Override  
  void someMethod() {
     ...
     Target t = getTarget(...);
     doSomethingWithTarget(t);
     ...
  }

  /** "Override" either this method or the next one. */
  private Target getTarget() {
    Target t = super.getTarget();
    targetStealer.accept(t);
    return t;
  }

  private void doSomethingWithTarget(Target t) {
    targetStealer.accept(t);
    super.doSomethingWithTarget(t);
  }
}

但是,這當然不可行,因為私有方法不能被覆蓋。 因此,下一種類型的方法將類似於ByteBuddyJavassist

public static class Interceptor {
  private final Consumer<Target> targetStealer;
  // ctor elided

  public  void doSomethingWithTarget(Target t) {
    targetStealer.accept(t);
  }
}


/** Using ByteBuddy. */
IFace byteBuddyBlackMagic(
    IFace iface /* known IFaceImpl*/,
    Consumer<Target> targetStealer) {
  return (IFace) new ByteBuddy()
      .subClass(iface.getClass())
      .method(ElementMatchers.named("doSomethingWithTarget"))
      .intercept(MethodDelegation.to(new Interceptor(t))
      .make()
      .load(...)
      .getLoaded()
      .newInstance()
}

/** Or, using Javassist */
IFace javassistBlackMagic(
    IFace iface /* known IFaceImpl*/,
    Consumer<Target> targetStealer) {
  ProxyFactory factory = new ProxyFactory();
  factory.setSuperClass(iface.getClass());
  Class subClass = factory.createClass();
  IFace = (IFace) subClass.newInstance();

  MethodHandler handler =
      new MethodHandler() {
        @Override
        public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
          if (thisMethod.getName().equals("doSomethingWithTarget")) {
            consumer.accept((Target) args[0]);
          }
          return proceed.invoke(self, args);
        }
      };
  ((ProxyObject) instance).setHandler(handler);
  return instance;
}

當我測試這些模式時,它在其他情況下仍然有效,即我想攔截的方法是本地程序包,但不適用於私有方法( 根據文檔 ,預期用於ByteBuddy)。

因此,是的,我認識到這是在試圖喚起黑暗力量,而這通常是令人皺眉的。 問題仍然存在,這可行嗎?

如果您可以在公共靜態void主塊中執行某些代碼,或者在加載IFaceImpl之前執行某些代碼,則可以在加載該類之前直接使用javassist編輯該類-這樣就可以將方法更改為公共方法,添加另一個方法,等等。 :

public class Main {
    public static void main(String[] args) throws Exception {
        // this would return "original"
//        System.out.println(IFace.getIFace().getName());
        // IFaceImpl class is not yet loaded by jvm
        CtClass ctClass = ClassPool.getDefault().get("lib.IFaceImpl");
        CtMethod getTargetMethod = ctClass.getDeclaredMethod("getTarget");
        getTargetMethod.setBody("{ return app.Main.myTarget(); }");
        ctClass.toClass(); // now we load our modified class

        // yay!
        System.out.println(IFace.getIFace().getName());
    }

    public static Target myTarget() {
        return new Target("modified");
    }
}

庫代碼是這樣的:

public interface IFace {
    String getName();
    static IFace getIFace() {
        return new IFaceImpl();
    }
}
class IFaceImpl implements IFace {
    @Override public String getName() {
        return getTarget().getName();
    }
    private Target getTarget() {
        return new Target("original");
    }
}
public class Target {
    private final String name;
    public Target(String name) {this.name = name;}
    public String getName() { return this.name; }
}

如果在加載該類之前無法執行代碼,則需要使用工具化,我將使用byte-buddy-agent庫來簡化此過程:

public class Main {
    public static void main(String[] args) throws Exception {
        // prints "original"
        System.out.println(IFace.getIFace().getName());

        Instrumentation instrumentation = ByteBuddyAgent.install();
        Class<?> implClass = IFace.getIFace().getClass();
        CtClass ctClass = ClassPool.getDefault().get(implClass.getName());
        CtMethod getTargetMethod = ctClass.getDeclaredMethod("getTarget");
        getTargetMethod.setBody("{ return app.Main.myTarget(); }");
        instrumentation.redefineClasses(new ClassDefinition(implClass, ctClass.toBytecode()));

        // yay!
        System.out.println(IFace.getIFace().getName());
    }

    public static Target myTarget() {
        return new Target("modified");
    }
}

由於模塊的工作方式,這兩個版本在Java 9及更高版本上運行都可能會有更多問題,您可能需要添加其他啟動標志。
請注意,在Java 8上,客戶端JRE可能沒有工具化。 (但即使在運行時也可以添加更多的hack)

使用javassist,您可以在IClassImpl類中對someMethod()進行檢測,以將TargetClass的實例發送到其他類並將其存儲在其他類中,或者使用創建的實例進行其他操作。

這可以使用javassist中的insertAfter()方法來實現。

例如 :

method.insertAfter( "TestClass.storeTargetInst(t)" ); // t is the instance of Target class in IClassImpl.someMethod

TestClass{ public static void storeTargetInst(Object o){ ### code to store instance ###} }

insertAfter()方法在方法的return語句之前或在空方法的情況下,將方法的最后一行插入代碼行。

有關可用於儀器的方法的更多信息,請參考此鏈接 希望這可以幫助!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM