我有一个包类私有方法的库类.通过子类直接覆盖此方法是不可选的.有没有办法,无论多么丑陋,执行自己的代码,当这个包私有方法从库内调用,例如使用AspectJ?
这是类的简化示例(packagePrivateMethod()实际上不是直接调用,而是从本机代码调用):
public LibClass { public LibClass() { ... packagePrivateMethod(); ... } void packagePrivateMethod() { // <-- here I want to execute additional code ... } }
解决方法
你可以使用相当重的方法.
写一个小的Java代理SO post about that topic.
>使用提供的Instrumentation interface拦截类加载
>使用字节码修改库(例如ASM或Java Assist(仅限Java 6!))来调整字节码(例如用任何您想要做的替换方法调用).
这样可以修改所有内容的字节码,但是在执行前需要修改该字节码.
当然,您也可以通过修改类文件来静态地执行此操作,将现有的字节代码替换为您在上述步骤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>>>!"); } }
运行示例的命令(您必须将代理放在具有属性Premain-Class的清单的jar中: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