添加代码以打包私有库方法

时间:2013-11-23 10:30:29

标签: java aspectj

我有一个包含私有方法的库类。通过子类直接覆盖此方法是没有选择的。当从库内部调用这个包私有方法时,有没有办法,无论多么难看,执行自己的代码,例如使用AspectJ?

以下是该类的简化示例(packagePrivateMethod()实际上不是直接调用,而是来自本机代码):

public LibClass {

  public LibClass() {
    ...
    packagePrivateMethod();
    ...
  }

  void packagePrivateMethod() {
    // <-- here I want to execute additional code
    ...
  }
}

5 个答案:

答案 0 :(得分:6)

您可以使用相当重量级的方法。

  1. 编写一个小型Java代理SO post about that topic
  2. 使用提供的Instrumentation interface拦截课程加载
  3. 使用字节码修改库(例如ASMJava Assist(仅限Java 6!))来检测字节代码(例如,用您真正想做的任何内容替换方法调用。< / LI>

    这可以工作,因为您可以修改所有内容的字节代码,但它需要您在执行之前修改该字节代码。

    当然,您也可以通过修改类文件静态地执行此操作,将现有字节代码替换为您在上面的步骤3中创建的字节代码。

    如果您不想/不能静态替换类的字节代码,则必须在运行时修改字节码。对于使用Java代理来说,这是一个好主意。

    因为直到现在这都是抽象的,所以我添加了一个示例,它将拦截您的库类的加载,在包私有方法中注入一个方法调用。当main方法执行时,您可以从输出中看到,在库类的代码之前直接调用注入的方法。如果添加return;作为注入的代码,您也可以完全阻止该方法的执行。

    以下是使用Java 6和JavaAssist解决问题的示例代码。如果你想沿着那条路走下去并使用像Java 7这样的新东西,你就必须用ASM替换字节码操作。这有点不太可读,但也不完全是火箭科学。

    主要班级:

    package com.aop.example;
    
    public class Main {
    
      public static void main(String[] args) {
        System.out.println("Main starts!");
        LibClass libClass = new LibClass();
        System.out.println("Main finished!");
      }
    }
    

    你的LibClass:

    package com.aop.example;
    
    public class LibClass {
    
      public LibClass() {
        packagePrivateMethod();
      }
    
      void packagePrivateMethod() {
        // <-- here I want to execute additional code
        System.out.println("In packagePrivateMethod");
      }
    }
    

    代理人:

    package com.aop.agent;
    
    import java.io.IOException;
    import java.lang.instrument.ClassFileTransformer;
    import java.lang.instrument.IllegalClassFormatException;
    import java.lang.instrument.Instrumentation;
    import java.security.ProtectionDomain;
    
    import javassist.CannotCompileException;
    import javassist.ClassPool;
    import javassist.CtClass;
    import javassist.CtMethod;
    import javassist.LoaderClassPath;
    import javassist.NotFoundException;
    
    public class Agent {
    
      public static void premain(String agentArgs, Instrumentation instr) {
        System.out.println("Agent starts!");
        instr.addTransformer(new ClassFileTransformer() {
    
          @Override
          public byte[] transform(ClassLoader classLoader, String className, Class<?> arg2, ProtectionDomain arg3,
              byte[] bytes)
              throws IllegalClassFormatException {
            System.out.println("Before loading class " + className);
    
            final String TARGET_CLASS = "com/aop/example/LibClass";
    
            if (!className.equals(TARGET_CLASS)) {
              return null;
            }
    
            LoaderClassPath path = new LoaderClassPath(classLoader);
            ClassPool pool = new ClassPool();
            pool.appendSystemPath();
            pool.appendClassPath(path);
    
            try {
              CtClass targetClass = pool.get(TARGET_CLASS.replace('/', '.'));
              System.out.println("Enhancing class " + targetClass.getName());
              CtMethod[] methods = targetClass.getDeclaredMethods();
              for (CtMethod method : methods) {
                if (!method.getName().contains("packagePrivateMethod")) {
                  continue;
                }
                System.out.println("Enhancing method " + method.getSignature());
                String myMethodInvocation = "com.aop.agent.Agent.myMethodInvocation();";
                method.insertBefore(myMethodInvocation);
              }
              System.out.println("Enhanced bytecode");
    
              return targetClass.toBytecode();
            }
            catch (CannotCompileException e) {
              e.printStackTrace();
              throw new RuntimeException(e);
            }
            catch (IOException e) {
              e.printStackTrace();
              throw new RuntimeException(e);
            }
            catch (NotFoundException e) {
              e.printStackTrace();
              throw new RuntimeException(e);
            }
          }
    
        });
      }
    
      public static void myMethodInvocation() {
        System.out.println("<<<My injected code>>>!");
      }
    }
    

    运行示例的命令(您必须将代理放入jar,其清单具有属性Premain-Class: com.aop.agent.Agent

    %JAVA_HOME%\bin\java -cp .;..\javassist-3.12.1.GA.jar -javaagent:..\..\agent.jar com.aop.example.Main
    

    此示例的输出运行如下命令:

    Agent starts!
    Before loading class com/aop/example/Main
    Main starts!
    Before loading class com/aop/example/LibClass
    Enhancing class com.aop.example.LibClass
    Enhancing method ()V
    Enhanced bytecode
    <<<My injected code>>>!
    In packagePrivateMethod
    Main finished!
    Before loading class java/lang/Shutdown
    Before loading class java/lang/Shutdown$Lock
    

答案 1 :(得分:3)

您可以使用Mockito或类似的模拟库来模拟包私有方法。例如:

// declared in a package
public class Foo {
    String foo(){
        return "hey!";
    }
}

@Test
public void testFoo() throws Exception {
    Foo foo = Mockito.spy(new Foo());

    Assert.assertEquals("hey!", foo.foo());

    Mockito.when(foo.foo()).thenReturn("bar!");

    Assert.assertEquals("bar!", foo.foo());

}

答案 2 :(得分:1)

您可以将Spring添加到项目中吗? 可以使用ProxyFactory - 请参阅another SO post

使用ProxyFactory,您可以add an advice获取类实例,并将方法执行委托给另一个类(使用packagePrivateMethod()和/或用您想要的代码替换它。)

由于库不是弹簧管理的,您可能必须使用弹簧加载时编织:ltw xml & examples

答案 3 :(得分:1)

使用装饰器模式。它专门针对这种情况而设计。如果您需要更多详细信息,请先ping我,然后选中this

或者您也可以使用反射或字节码操作机制在运行时动态创建类型。

答案 4 :(得分:1)

另一个想法是:在同一个包中创建一个具有相同名称的新类。

假设您要替换以下项目中的LibraryClass

Project structure:
  - library.jar (contains com.example.LibraryClass)
  - src
    - com
      - mycompany
        - MyClass.java

只需创建具有相同名称的包和文件即可。

Project structure:
  - library.jar (contains com.example.LibraryClass)
  - src
    - com
      - mycompany
        - MyClass.java
      - example
        - LibraryClass.java  <- create this package and file

这依赖于类加载器获取文件而不是库的文件,但如果你只是试图让hack工作进行测试,那么值得一试。我不确定类加载器如何决定加载哪个文件,因此这可能不适用于所有环境。

如果您没有LibraryClass的源代码,只需复制反编译的代码,然后进行更改。

对于我需要这种能力的项目,它只是一些测试原型代码......我不需要任何生产质量,或者在所有环境中工作。