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 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将这种能力无限升级到让某个对象的拥有某个属性,而且这个属性的赋值可以千变万化。
大白话就是注解可以带参数了,这几乎要开一开脑洞才好理解,结合下面这个实例。( https://www.educba.com/java-annotations/ )
e.g.2.1. MagicianAnnotation.java: 自定义一个注解类,该注解带有两个参数,分别给了缺省默认值
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MagicianAnnotation {
String Wizard() default "Reynolds";
String House() default "Green";
}
e.g.2.2. MagicianObject.java: 创建一个对象类,使用MagicianAnnotation修饰,该对象类有一个方法 getString, getString也使用注解MagicianAnnotation进行修饰,且带有两个参数
@MagicianAnnotation
public class MagicianObject {
@MagicianAnnotation(Wizard = "Harry Potter", House = "Red")
public String getString() { return null; }
}
e.g.2.3. MagicianTest.java: 创建一个测试类,运行MagicianObject, 观测被MagicianAnnotation所修饰的情况
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
public class MagicianObjectTest {
public static void main(String[] args) throws NoSuchMethodException, SecurityException {
new MagicianObject();
Class<Magician> magic = MagicianObject.class;
readAnnotationOn(magic);
Method method = magic.getMethod("getString", new Class[]{});
readAnnotationOn(method);
}
static void readAnnotationOn(AnnotatedElement element){
try{
System.out.println("\n Find annotations on " + element.getClass().getName());
Annotation[] annotations = element.getAnnotations();
for (Annotation annotation : annotations){
if (annotation instanceof MagicianAnnotation){
MagicianAnnotation mData = (MagicianAnnotation) annotation;
System.out.println("Wizard Name :" + mData.Wizard() + " , House Color :" + mData.House());
}
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
该测试程序预期运行的结果大致为:
$ java MagicianObjectTest
Find annotations on java.lang.Class
Wizard Name :Reynolds , House Color :Green
Find annotations on java.lang.reflect.Method
Wizard Name :Harry Potter , House Color :Red
实例2表明,引入了自定义的注解之后,在原代码层面的注解及其属性、赋值等,最终会在运行时分别读取到,依据这些设置、配置的属性及赋值,同样的代码可能会产生千变万化的运行结果。
这种魔法虽然赋予了开发者丰富的技能,却也为程序的追踪设置了陷阱,正如我们在各种Java开发手册( https://ufqi.com/news/ulongpage.1510.html?tit=Java開發手冊-阿里巴巴-嵩山版-3:常量定义 )中,都敦敦教诲不要在程序源代码中留置“魔法”。
【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。
所以,本意上理解,Java Annotation注解是对注释的丰富和扩展,它的边界不应该扩展到影响程序运行逻辑上去,注解的根本仍然是像内置的那几个作用那样( @Override, @Deprecated, @SuppressWarnings等等 ),是帮助理解代码,而不是修改变更、路由导向程序业务逻辑本身。
从这个意义上来说,Java Spring全家桶等剑走偏锋似乎走得有些远了。
回到起初,希望这两个实例能够清楚的解释了Java Annotations注解的来龙去脉,如同菜刀可以切菜也可以杀人一样,注解可以帮助开发者理解代码,也可以通过Reflect反射技术对程序的运行逻辑进行修改变更、路由导向等等。理解了这些,再去看Java Spring框架中的 @RequestXXX 就会豁然开朗,不过尽管知道了基本原理,要弄清楚每一个自定义的注解的具体含义,还需要进一步地解读源代码或技术手册。
回到上面的技术栈分析图,一个相对简单的Java Web应用CRM系统,如果基于GWA2 Java 进行构建,直接就是Tomcat跑起来一个应用即可。而如果看上面的供应链,就显得复杂无比,各种封装和嵌套多达4-5层,而每一层嵌套又引入更多的第三方组件,由是一个简单的应用变得复杂无比。在其成本上升的同时,其开发门槛也显得高高在上了,几乎没有一个人能够全部掌握所涉及的全部组件。对每一个组件不说去拆解分析源代码,恐怕操作使用说明文档能看完整了也非易事。
供应链越长,质量越难控制。
在立即可用小步快跑快速迭代风行的时期,可能关乎质量、性能、安全等皆居于次要地位。

-GWA2 吉娃兔 是”通用网络应用架构( General Web Application Architeture, https://ufqi.com/dev/gwa2/ )”,基于 -GWA2 可以轻便构建各种网络应用程序,
包括复杂的在线购物商城、在线医疗、在线教育、 旅游交易平台、社群或者社交网站和新闻资讯网站等,
也包括各种企事业单位网上门户,在线交互及服务作业系统等.
还可以包括为NativeApp做服务器端支持, 甚至是WebApp的全部.
-GWA2 是为数不多的支持跨开发语言的应用框架,目前支持 -Java, -PHP, -Perl, -Aspx and -Python .
-GWA2 is a “General Web Application Architecture” and based on -GWA2 developers can easily build a variety of network applications,
including complex online shopping malls, online medical services, online teaching, travel trading platforms, community or social networking sites and news information sites, etc.
Also the applications include various online portals of enterprises and institutions, online interaction and service operations systems.
Moreover it contains server-side support for Native App, or even all of the WebApp.
-GWA2 is one of the web frameworks which provide cross-language support for -Java, -PHP, -Perl, -Aspx and -Python at present.
-GWA2 is E.A.S.Y
Easy Along, Swift Yield
轻松启动, 快速产出.











Pingback引用通告: 两个实例解释清楚Java Annotations注解 | -wordpress-wadelau