字节码注入在哪里发生?

ipa*_*lic 2 annotations bytecode classloader bytecode-manipulation

动机

我有一个SomeObject.java文件:

class SomeObject {
   String name;
}
Run Code Online (Sandbox Code Playgroud)

编译它会创建一个包含字节码的SomeObject.class文件.

0xCAFEBABE...
Run Code Online (Sandbox Code Playgroud)

如果我们在JVM上使用SomeObject,它将由当前的类加载器加载,并且一切正常.

现在让我们假设我想要一些动态代码生成.我可以写我的自定义注释

@Target(ElementType.TYPE)
public @interface Data {
   ...
}
Run Code Online (Sandbox Code Playgroud)

并将其作为修饰符添加到类声明中:

@Data
class SomeObject {
   String name;
}
Run Code Online (Sandbox Code Playgroud)

我也可以在运行时保留它@Retention(RetentionPolicy.RUNTIME):

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Data {
   ...
}
Run Code Online (Sandbox Code Playgroud)

用于字节码注入的注释在哪里?在使用适当的运行时保留注释加载类时,类加载器是否会注入字节码,如下图所示:

source -(compile)-> bytecode -(classloader bytecode injection)-> injected bytecode -(classloading)-> JVM loaded bytecode   
Run Code Online (Sandbox Code Playgroud)

pab*_*tes 6

是的,可以让你的自定义类加载器加载一个类,并通过字节码操作工具(如JavassistASM)执行修改,加载到内存中而不是类文件中的字节码,而是加载到修改后的字节码中.虽然有更容易(并且在我看来更好)的方式.

注释处理器工具(APT)

从Java 6开始,你有了APT,它允许你挂钩编译过程(通过-processor javac中的参数).使用APT,您可以访问代码的AST(抽象语法树),并且可以在使用javax.lang.model进行编译时直接执行修改.这意味着将使用所需的修改生成您的类文件.

在这种情况下,链将类似于:

source -(compile and performs modifications at model level)-> bytecode already modified - regular class loader -> loads class into memory

编译后处理

可以使用的另一种方法是在编译之后执行字节码注入作为后编译过程.在这种情况下,您使用字节码修改工具(再次使用javassist,asm等),它可以在找到所需的注释时执行所需的修改,生成带有注入的字节码的新类文件.

在这种情况下,您的链将是:

source -compile -> bytecode -post-compile-> modified bytecode - regular class loader -> loads class into memory

运行时修改

最后,我们达到运行时字节码修改.即使你的想法是可能的,在我看来我会留下类加载器魔术并使用Javassist这样的工具,它也允许你拥有可以修改和重新加载的动态代理.

在javassist特殊情况下,链将是

source -compile -> bytecode -post-compile-> modified bytecode - regular class loader -> loaded into memory - javassist proxy -> modified class - javassist hot swapper -> re-modified class

代理并不完美(没有什么是).您将获得性能提升,并且您将无法修改类的公共接口(旁注:APT和后编译过程都可以允许您修改类公共接口).我可以对此进行更多介绍,但我认为这已经足以让您深思熟虑.如果您需要其他信息,请随时发表评论.