gpt4 book ai didi

java - 如何将方法调用注入(inject)android中的另一个方法

转载 作者:行者123 更新时间:2023-12-03 05:49:27 25 4
gpt4 key购买 nike

我有一个类,

  • 一个名为 something 的字段,
  • 一个名为 setSomething 的 setter 方法, 和,
  • 一个名为 onChange 的方法每次都应该调用 something被改变。

  • 我希望能够自由地添加更多字段并为所有字段具有相同的行为。

    我不想手动调用 onChange因为,
  • 很多样板,
  • 代码将用 Kotlin 编写,所以我根本不想编写 setter 函数。

  • 我能想到的理想解决方案是以某种方式注入(inject) onChangereturn 之前调用电话对于编译时的每个 setter 方法。

    我看过注释处理,但显然在那个阶段实际上并没有编译类,所以我必须重新生成整个类?我不完全明白这一点。

    另一种选择似乎是编写一个 gradle 插件,它将找到相关的类并修改它们的字节码。

    实际上,我已经将其作为一个纯 Java 项目开始工作(gradle 插件已半完成),并且能够找到类并注入(inject)方法调用。虽然似乎无法成功将结果写入类文件。

    这是我所拥有的(使用 BCEL ):
    public class StateStoreInjector {

    public static void main(String[] args) {
    // Find all classes that extends StateStore
    Reflections reflections = new Reflections("tr.xip.statestore");
    Set<Class<? extends StateStore>> classes = reflections.getSubTypesOf(StateStore.class);
    for (Class c : classes) {
    try {
    JavaClass clazz = Repository.lookupClass(c.getName());
    JavaClass superClazz = Repository.lookupClass(StateStore.class.getName());
    if (Repository.instanceOf(clazz, superClazz)) {
    injectInClass(clazz, superClazz);
    }
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    }
    }
    }

    private static void injectInClass(JavaClass clazz, JavaClass superClazz) {
    ClassGen classGen = new ClassGen(clazz);
    ConstantPoolGen cp = classGen.getConstantPool();

    // Find the onChange method
    Method onChangeMethod = null;
    for (Method m : superClazz.getMethods()) {
    if (m.getName().equals("onChange")) {
    onChangeMethod = m;
    }
    }

    if (onChangeMethod == null) {
    throw new RuntimeException("onChange method not found");
    }

    ClassGen superClassGen = new ClassGen(superClazz);
    ConstantPoolGen superCp = superClassGen.getConstantPool();

    // Add onChange method ref to the class ConstantPool
    MethodGen onChangeMethodGen = new MethodGen(onChangeMethod, superClassGen.getClassName(), superCp);
    cp.addMethodref(onChangeMethodGen);

    // Loop through all methods to inject method invocations if applicable
    for (Method m : clazz.getMethods()) {
    // Skip methods with names shorter than 3 chars - we're looking for setters and setters would be min 4 chars
    if (m.getName().length() < 3) continue;

    // Check if the method actually starts with the keyword "set"
    boolean isSetMethod = m.getName().substring(0, 3).toUpperCase().equals("SET");
    // Get method name without the "set" keyword
    String methodName = m.getName().substring(3, m.getName().length());

    // Check that we actually have a field set by this setter - that this setter is "valid"
    boolean fieldWithSameNameExists = false;
    for (Field f : clazz.getFields()) {
    if (f.getName().toUpperCase().equals(methodName.toUpperCase())) {
    fieldWithSameNameExists = true;
    break;
    }
    }

    // Proceed with injection if criteria match
    Method newMethod = null;
    if (isSetMethod && fieldWithSameNameExists) {
    newMethod = injectInMethod(m, onChangeMethodGen, classGen, cp);
    }

    // Injection returned. Do we have a new/modified method? Yes? Update and write class.
    if (newMethod != null) {
    classGen.removeMethod(m);
    classGen.addMethod(newMethod);
    classGen.update();
    try {
    String packageName = clazz.getPackageName().replace(".", "/");
    String className = clazz.getClassName();
    className = className.substring(className.lastIndexOf(".") + 1, className.length());
    clazz.dump(packageName + "/" + className + "Edited.class");
    }
    catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
    }

    private static Method injectInMethod(Method m, MethodGen onChangeMethodGen, ClassGen cg, ConstantPoolGen cp) {
    MethodGen methodGen = new MethodGen(m, cg.getClassName(), cp);
    InstructionList il = methodGen.getInstructionList();

    println(il.toString() + "pre insert ^");

    // Find the "return" instruction
    Instruction returnInstruction = null;
    for (Instruction i : il.getInstructions()) {
    if (i.getOpcode() == 177) returnInstruction = i;
    }
    // If found, insert onChange invocation instruction before the return instruction
    if (returnInstruction != null) {
    int index = cp.lookupMethodref(onChangeMethodGen); // Find the index of the onChange method in the CP
    il.insert(returnInstruction, new INVOKEVIRTUAL(index)); // Insert the new instruction

    println(il.toString() + "post insert ^");

    il.setPositions(); // Fix positions

    println(il.toString() + "post set pos ^");

    il.update();
    methodGen.update();

    return methodGen.getMethod();
    }

    return null;
    }

    private static void println(String message) {
    System.out.println(message);
    }
    }

    输入Java类:
    public class DummyStateStore extends StateStore {
    private int id = 4321;

    public void setId(int id) {
    this.id = id;
    }

    public int getId() {
    return id;
    }
    }

    父商店类:
    public class StateStore {

    public void onChange() {
    // notifies all subscribers
    }
    }

    输出(反编译)类文件:
    public class DummyStateStore extends StateStore {
    private int id = 4321;

    public DummyStateStore() {
    }

    public void setId(int id) {
    this.id = id;
    }

    public int getId() {
    return this.id;
    }
    }

    日志输出:
       0: aload_0[42](1)
    1: iload_1[27](1)
    2: putfield[181](3) 2
    5: return[177](1)
    pre insert ^
    0: aload_0[42](1)
    1: iload_1[27](1)
    2: putfield[181](3) 2
    -1: invokevirtual[182](3) 26
    5: return[177](1)
    post insert ^
    0: aload_0[42](1)
    1: iload_1[27](1)
    2: putfield[181](3) 2
    5: invokevirtual[182](3) 26
    8: return[177](1)
    post set pos ^

    (我通过调试代码检查了索引26,它是CP中的正确索引)

    现在,问题是:
  • 为什么在反编译的代码中看不到调用,但它似乎已添加到指令列表中?我错过了什么?
  • 我将在哪里导出 android 构建中修改后的类文件,以便将它们包含在最终的 apk 中?
  • 最佳答案

    您正在尝试使用反射,但 Kotlin 不需要这样做,因为您可以创建高阶函数(将函数作为输入的函数)。

    您可以执行以下操作:

    class ChangeableType<T>(private var value: T, private val onChange: () -> Unit) {

    fun set(value: T) {
    this.value = value
    this.onChange.invoke()
    }
    }

    class MyRandomClass() {
    val something = ChangeableType(0, { System.print("Something new value: $value") })
    val anotherThing = ChangeableType("String", { System.print("Another thing new value: $value") })
    }

    class ConsumingClass {
    val myRandomClass = MyRandomClass()

    fun update() {
    myRandomClass.apply {
    something.set(1)
    anotherThing.set("Hello World")
    }
    }
    }

    关于java - 如何将方法调用注入(inject)android中的另一个方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48542054/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com