Can an annotation type define static methods?
我开发了一个框架和相应的API,其中包括一个运行时可见的注释。API还提供了一些帮助器方法,供客户端在类具有该注释的对象上使用。可以理解,助手与注释紧密耦合,但是从客户机封装它们的内部是很重要的。帮助器方法当前是通过注释类型中的静态内部类提供的…
1 2 3 4 5 6 7 8 9 10 11 | @Target(TYPE) @Retention(RUNTIME) public @interface MyAnnotation { // ... annotation elements, e.g. `int xyz();` ... public static final class Introspection { public static Foo helper(Object mightHaveMyAnnotation) { /* ... uses MyAnnotation.xyz() if annotation is present ... */ } } } |
…但是这些助手也可以很容易地存在于其他顶级实用程序类中。任何一种方法都可以从客户机代码中提供必要的封装量,但这两种方法都需要额外的成本来维护完全独立的类型,防止它们实例化,因为所有有用的方法都是静态的,等等。
当Java 8在Java接口类型上引入静态方法时(参见JLS 9.4),该特性被吹捧为提供……的能力。
... organize helper methods in your libraries; you can keep static methods specific to an interface in the same interface rather than in a separate class.
— from Java Tutorials Interface Default Methods
这在JDK库中被用来提供实现,如
由于当前用于注释类型的jvm字节码表示与普通接口密切相关,我想知道注释是否也支持静态方法。当我将助手移动到注释类型中时,例如:
1 2 3 4 5 6 7 | @Target(TYPE) @Retention(RUNTIME) public @interface MyAnnotation { // ... annotation elements ... public static Foo helper(Object mightHaveMyAnnotation) { /* ... */ } } |
…javac抱怨了以下编译时错误,这让我有点惊讶:
openjdk runtime environment 18.3(build 10+46)
- modifier static not allowed here
- elements in annotation type declarations cannot declare formal parameters
- interface abstract methods cannot have body
显然,Java语言目前不允许这样做。这可能是因为有很好的设计理由不允许这样做,或者正如前面对静态接口方法所假定的那样,"没有令人信服的理由这样做;一致性不足以改变现状"。
这个问题的具体目的不是问"为什么它不起作用?"或者"语言应该支持它吗?"以避免基于意见的回答。
JVM是一种强大的技术,在许多方面比Java语言所允许的更灵活。同时,Java语言也在不断发展,今天的答案可能会在明天过时。理解到这种力量必须谨慎使用…
在技术上是否可以将静态行为直接封装在注释类型中,以及如何封装?在JVM内实现这一技术并与标准Java代码互操作在技术上是可行的,但它具有重要的注意事项:
一个概念证明是成功的,它使用了一种直接操作JVM字节码的机制。
这个机制很简单。使用另一种语言或字节码操作工具(即ASM),它将发出JVM EDCOX1×0×文件,两者(1)匹配合法Java(语言)注释的功能和外观,(2)还包含具有EDCOX1×1的访问修改集的所需方法实现。该类文件可以单独编译并打包成jar或直接放置在类路径上,此时它可以被您的其他正常Java代码使用。
下面的步骤将创建对应于以下不太合法的Java注释类型的工作字节码,该类型定义了POC中简单的EDCOX1×2静态函数;
1 2 3 4 5 6 7 8 9 10 |
首先,将带有"normal"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import static org.objectweb.asm.Opcodes.*; import java.util.*; import org.objectweb.asm.*; import org.objectweb.asm.tree.*; /* ... */ final String fqcn ="com.example.MyAnnotation"; final String methodName ="strlen"; final String methodDesc ="(Ljava/lang/String;)I"; // int function(String) ClassNode cn = new ClassNode(ASM6); cn.version = V1_8; // Java 8 cn.access = ACC_SYNTHETIC | ACC_PUBLIC | ACC_INTERFACE | ACC_ABSTRACT | ACC_ANNOTATION; cn.name = fqcn.replace(".","/"); cn.superName ="java/lang/Object"; cn.interfaces = Arrays.asList("java/lang/annotation/Annotation"); // String value(); cn.methods.add( new MethodNode( ASM6, ACC_PUBLIC | ACC_ABSTRACT,"value","()Ljava.lang.String;", null, null)); |
如果合适,也可以用
1 2 3 4 5 6 | AnnotationNode runtimeRetention = new AnnotationNode(ASM6,"Ljava/lang/annotation/Retention;"); runtimeRetention.values = Arrays.asList( "value", // parameter name; related value follows immediately next: new String[] {"Ljava/lang/annotation/RetentionPolicy;","RUNTIME" } // enum type & value ); cn.visibleAnnotations = Arrays.asList(runtimeRetention); |
接下来,添加所需的
1 2 3 4 5 6 7 8 9 10 11 | MethodNode method = new MethodNode(ASM6, 0, methodName, methodDesc, null, null); method.access = ACC_PUBLIC | ACC_STATIC; method.annotationDefault = Integer.MIN_VALUE; // see notes AbstractInsnNode invokeStringLength = new MethodInsnNode(INVOKEVIRTUAL,"java/lang/String","length","()I", false); method.instructions.add(new IntInsnNode(ALOAD, 0)); // push String method arg method.instructions.add(invokeStringLength); // invoke .length() method.instructions.add(new InsnNode(IRETURN)); // return an int value method.maxLocals = 1; method.maxStack = 1; cn.methods.add(method); |
最后,将此注释的jvm字节码输出到类路径上的
1 2 3 | ClassWriter cw = new ClassWriter(0); cn.accept(cw); byte[] bytecode = cw.toByteArray(); |
笔记: