注解这玩意

注解&东西

注解,是什么滴干活?注解,是一种神奇的东西,您可以在类、接口、方法、变量身前添加注解(形式是一个’@’符号后面带上注解名,有时候还可以带上类似参数一样的玩意),之后呢,注解就会发生化学/物理反应,在您的代码里乱搅和,正所谓出其不意,搞你一把,神出鬼没,用行内的黑话说,这叫『打注解』。

人说,活久见,活得久了,就什么都见过了。什么人,或者说,什么情况下会拎出注解?使用注解的人就像使用MacBook的人,要么是小白,要么是绝顶高手。

小白的人呢,最喜用@Override,您问他:这个注解是干什么的呀?他会回答:哦,Override声明了该方法是接口方法,重写方法。
(毫无疑问,邱永臣属于小白行列)

那些高手们呢,根本不屑于使用什么@Override/@Deprecated,心想:哼,吾等圣贤之才也,岂与子相同并论?然后撇开官方已有的Override/Deprecated/SuppressWarnings,自定义注解,所以,您会在各大开源框架里头见到大量注解,不要惊讶。

举Spring为例,大量的 @Service / @Autowired / @Component / @Source(与java自带的重复) / @Qualified,还有更著名的Restful @Get(“User/info”)…
一个例子不够看,再举lombok为例。不开化的同学,喜欢写很多Getter/Setter,或是自己实例化logger,已经开化的同学呢,中意安装lombok插件,填上几个 @Data / @Getter / @Setter / @Slf4j,开开心心地在一旁偷懒。
还有,切面!最常见的切面,非日志切面莫属。想一想,打一个 @GiveMeSomeLog,日志文件里凭空变魔术一样,多出运行上下文、方法参数、函数执行状态等等信息,用两个字回答我,厉不厉害?
除此之外,注解被某些童鞋用来当做校验数据的利器。比如,在某个DTO的某个字段前边,明码标价,以强硬的姿态,声明字段的合法取值范围 @INeedYouToBe(min=0, max=9),这也是极厉害的。

到这里,我们可以得到结论:
注解是一种用来声明『本注解所在之处,虽不会片甲不留,却也有神奇之事发生』的手段。

可是,注解的形式是什么?是接口,是类,是方法,还是变量?
为什么在运行环境里,注解会起作用?编译器认识注解么?JVM认识注解么?
注解在编译后,以什么形式存在?单独的class文件?
为什么注解一定要以 @ 符号开头,用英文单词qiuyongchen开头可不可以?(强迫症惧怕@)
打上注解后,为什么会发生神奇之事?(打上@Getter,谁赋予Bean一个Getter?)
注解能用在哪些地方?什么人有资格拥有注解?类?方法?对象?
注解能活到什么时候?编译后就不见了?运行时能捕捉到注解么?

正所谓,『People live to deal with problem, if you don’t have problems to deal with, you’re probably 』人活着,就是为了解决问题,为了搞注解,我们需要提出很多问题。

为了回答这些问题,我们需要海量的知识背景,不用急,请听我慢慢道来:那是很久很久以前….

注解&本质

注解本质上是什么呢?
无头绪时,可以从自定义注解追查线索,一般,自定义注解会用以下形式:

1
2
3
4
5
6
7
8
9
10
11
12
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface MethodInfo {

String author() default "xxx@gmail.com";

String date();

int version() default 1;
}

自定义MethodInfo注解,注意,MethodInfo类型不是interface,不是class,而是,奇怪的@interface。莫名其妙哦,搞个@interface出来干嘛?不像接口不像类,二不像咩。
(这里我们就要开始悲天悯人了,您瞧,Java的大师们,已经没方法了,如果注解是interface,开发者需要搞出implements,如果注解是class,开发者需要实现方法,也不能指定默认值 default “trinea@gmail.com”,没方法,新开一套规则吧,定义为@interface,毕竟看起来比较像接口)

实际上,自定义注解,@interface确实创建了一个接口,只不过这个接口有点特殊,规则特殊,用途特殊,是接口中的一朵奇葩。
更神奇的是,自定义注解时,该注解默认隐式扩展java.lang.annotation.Annotation接口。默认隐式扩展?什么意思呢?意思就是Java官方霸气地站在你面前,嚣张的说:”小子,你是开发者,扩展接口时必须使用extend,而注解是我们亲儿子,它不用使用extend也能扩展其它接口!注解自打生下来那一刻起,就拥有它老爹Annotation的一切了,不用嫉妒了,死心吧”

到这里,我们可以得到结论:
注解是特殊的接口

知道注解是特殊的接口,对应的实现在何方?(有歌唱:我的爱人哪,你在何方?又有诗云:天涯何处无芳草。)
为了追查到注解的实现,可从Method类入手。
注解分3种:源码级(lombok)、字节码级、运行时级(Spring及各大框架)。运行时级别的注解运用了Java的反射(最劲爆的功能是’猜测’出一个对象的方法,并执行它!),借助Method类,获取到对象里边绑定的乱七八糟的注解,通常会是下边的形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void parseMethodAnnotations() {
for (Annotation methodAnnotation : method.getAnnotations()) {
Class<? extends Annotation> annotationType = methodAnnotation.annotationType();
RestMethod methodInfo = null;

for (Annotation innerAnnotation : annotationType.getAnnotations()) {
if (RestMethod.class == innerAnnotation.annotationType()) {
methodInfo = (RestMethod) innerAnnotation;
break;
}
}
……
}
}

method从哪儿来?呵呵,自然是用反射从对象身上搜刮得到的。我们找到method的源码java.lang.reflect.Method类,看看getAnnotation方法的实现:

1
2
3
4
5
6
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
if (annotationClass == null)
throw new NullPointerException();

return (T) declaredAnnotations().get(annotationClass);
}

再追一下declaredAnnotations()和对应的对象:

1
2
3
4
5
6
7
8
9
10
11
private transient Map<Class<? extends Annotation>, Annotation> declaredAnnotations;

private synchronized Map<Class<? extends Annotation>, Annotation> declaredAnnotations() {
if (declaredAnnotations == null) {
declaredAnnotations = AnnotationParser.parseAnnotations(
annotations, sun.misc.SharedSecrets.getJavaLangAccess().
getConstantPool(getDeclaringClass()),
getDeclaringClass());
}
return declaredAnnotations;
}

追查到这里,案件的线索已很明显(报告警察叔叔,就是他!就是他把枪塞在我的手上的)。
前后结合,城乡结合,古今结合,那开眼的同学就会发现,打在方法身上的子弹,不,打在方法身上的注解,编译完事后,以扩展并实现Annotation接口的某种’对象’的形式存在,依附存活在方法里面,成为了方法的’属性’。

到这里,我们可以得到结论:
注解是特殊的类

注解&化学物理反应

要自定义注解,需要三部分内容,也就是邱永臣所说的『注解三重奏』。

声明注解

注解姓甚名谁,家里有几口人,几头牛,几亩地等等信息,均在此昭告天下,比如:

1
2
3
4
5
6
7
8
9
10
11
12
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface MethodInfo {

String author() default "xxx@gmail.com";

String date();

int version() default 1;
}

声明使用

注解是把骇人的钝剑(既然是钝的,如何骇人?),无所不往,杀人不见血,落地不开花。但剑是不会主动杀人的,你得告诉它一个目标,它才能远程制导,瞄准,点火发射,华丽升空,绽放绝美的烟火。

1
2
3
4
@MethodInfo(date = "2017-02-07")
public String getAppName() {
return "nothing";
}

这样呢,注解才能getAppName身上”搞搞震”,演绎出它自身的艺术。

注解艺术

您给注解指定了目标,需要注解执行什么动作呢?这就因人而异了,可以仅仅是统计目标的存活数据,也可以建立超巨型烟雾罩,让目标永无见光之日,可以采取的动作太多了,三万年也数不完。
大体的思路,就是利用反射,找到注解所在的目标,为所欲为?此地无银三百两,邻人阿二不曾偷。

参考资料

  1. Java核心技术卷二
  2. Java注解(Annotation)
  3. Java深度历险(六)——Java注解
  4. 公共技术点之 Java 注解 Annotation