Java Annotations注解和Java Comments注释,英文里查别很大,在中文中一字之差让人颇为费解。Java Comments注释是随着Java语言的诞生就有的,意义明确,简单易懂,就是在源代码中的解释信息,通常用在帮助代码编写人员理解代码。Java Annotations出现的较晚,在Java 1.5(Java 5)中才引入,目前(2022年)Java部署的主流是Java 8(Java 1.8),最新发行版是Java 11(Java 1.11).
但Java Annotations注解的意义和作用就显得有些费解,比如官方文档中:
Annotations do not directly affect program semantics, but they do affect the way programs are treated by tools and libraries, which can in turn affect the semantics of the running program. Annotations can be read from source files, class files, or reflectively at run time.
Annotations注解不会直接影响程序的语义,但是他们确实影响工具和库处理程序的方式,进而影响运行中的程序的语义。Annotations注解可以从源文件 ,class文件,或者运行时的反射中读取。
随着“工具和库处理程序”的流行,比如在Java开发中逐渐流行的Spring全家桶等,处处可见的@RequestXXX 等表述,让后生Java Annotations逐渐走入开发者的视野。然则只言片语地要解释清楚Java Annotations注解是什么东西及怎么作用或者工作的,却非易事。
我们所主导的Java 开发框架GWA2 in Java( https://ufqi.com/dev/gwa2/ )并没有过多地倚重Java Annotations注解,主要是在初期技术架构选型时,我们考察了使用Reflection等相关技术时,发现其性能会下降安全受影响。
Due to 1) issues of performance and security of java.lang.reflection, We do not use it as routing or dynamic module invoking at present.
鉴于Reflection相关技术存在性能和安全相关方面问题,GWA2 in Java 目前没有考虑将其作为路由和动态模块加载技术手段。
然而,Java开发业界似乎形成了一种站在“巨人肩膀”上搭积木式地的堆叠,第一个扣子歪了大家也就一顺溜地继续歪下去,所谓性能和安全问题都是可以克服的,不能解决的部分也是在可接受范围内,皆大欢喜。
近期我们接手了两个Java开发的二期项目,基于Java Spring全家桶,项目技术栈虽然是Java,但其所依赖的各种第三方组件达到令人眼花缭乱的地步(如下图)。
Java Spring 组件套系
既然Java Annotations如此重要,自然要弄清楚,然而当我们试图向一些工程师解释Java Annotations是什么、怎么样的时候,发现并不容易,一方面是Java语言本身的复杂性,另一方面是Annotations是上层应用,是可选项。经过一番搜索、思考和探究,我觉得如下两个实例可以清晰地解释Java Annotations注解的基本原理和应用实践,回答了Java Annotations注解是什么,为什么和怎么样的问题。
实例1. 用Java Annotations注解标记某个对象类、实例、方法和属性具有或不具有某个属性。
这个实例来自官网 ( https://docs.oracle.com/javase/1.5.0/docs/guide/language/annotations.html )
e.g.1.1. SampleTestTag.java: 自定义一个Java Annotation注解对象,其中的 @Retention 和 @Target 是注解的注解,称之为“元注解”, 注解类的什么是在名称前加 “@” at符号
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SampleTestTag {
}
e.g.1.2. FooBar.java: 创建一个对象类 FooBar , 使用上面创建的Java注解 SampleTestTag
public class FooBar { @SampleTestTag public static void m1() { } public static void m2() { } @SampleTestTag public static void m3() { throw new RuntimeException("Boom"); } public static void m4() { } @SampleTestTag public static void m5() { } public static void m6() { } @SampleTestTag public static void m7() { throw new RuntimeException("Crash"); } public static void m8() { } }
e.g.1.3. FooBarTest.java: 创建一个测试程序,调用对象类 FooBar, 测试 SampleTestTag 的区分作用
import java.lang.reflect.*;
public class FooBarTest {
public static void main(String[] args) throws Exception {
int passed = 0, failed = 0;
for (Method m : Class.forName(args[0]).getMethods()) {
if (m.isAnnotationPresent(SampleTestTag.class)) {
try {
m.invoke(null);
passed++;
} catch (Throwable ex) {
System.out.printf("Test %s failed: %s %n", m, ex.getCause());
failed++;
}
}
}
System.out.printf("Passed: %d, Failed %d%n", passed, failed);
}
}
该测试程序预期运行的结果大致为:
$ java FooBarTest FooBar
Test public static void Foo.m3() failed: java.lang.RuntimeException: Boom
Test public static void Foo.m7() failed: java.lang.RuntimeException: Crash
Passed: 2, Failed 2
这个实例表示,通过自定义一个注解,可以将对象类的某些方法标记出来,核心方法是isAnnotationPresent。这只是一个简单的实例,如果这个路子走得通,有这种简单的方法或范式,可以将对象类、实例、方法和属性进行分类,仿佛打开了潘多拉的盒子,赋予开发者无穷尽的分类能力。
当具备这种能力后,可以在运行时根据分类,让A类的运行,让B类的都熄火;在英国区运行A类,在北美运行B类;让VIP的运行A类,让非VIP的运行B类;让小孩看A类,让老人看B类,其他人看C类….
显然这种能力是强大的,但同时其复杂性也显现出来了,开发者看到的代码和最终运行时的代码可能高度的 不–一–致。尽管代码都摆在哪里,但具体跑那些代码需要看运行时的状态。由此造成了可怕的所见非所得。
实例2. 用Java Annotations注解标记某个对象类、实例、方法和属性具有某个属性的某种赋值。
这是对实例1的进化和升级,如果实例1提供让某个对象具有或不具有,是与非的简单二元分类,则实例2将这种能力无限升级到让某个对象的拥有某个属性,而且这个属性的赋值可以千变万化。
更多全文内容: 两个实例解释清楚Java Annotations注解 | -UFQI-Blog , https://ufqi.com/blog/gwa2-java-annotations/