注解&东西 注解,是什么滴干活?注解,是一种神奇的东西,您可以在类、接口、方法、变量身前添加注解(形式是一个’@’符号后面带上注解名,有时候还可以带上类似参数一样的玩意),之后呢,注解就会发生化学/物理反应,在您的代码里乱搅和,正所谓出其不意,搞你一把,神出鬼没,用行内的黑话说,这叫『打注解』。
人说,活久见,活得久了,就什么都见过了。什么人,或者说,什么情况下会拎出注解?使用注解的人就像使用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身上”搞搞震”,演绎出它自身的艺术。
注解艺术 您给注解指定了目标,需要注解执行什么动作呢?这就因人而异了,可以仅仅是统计目标的存活数据,也可以建立超巨型烟雾罩,让目标永无见光之日,可以采取的动作太多了,三万年也数不完。 大体的思路,就是利用反射,找到注解所在的目标,为所欲为?此地无银三百两,邻人阿二不曾偷。
参考资料
Java核心技术卷二
Java注解(Annotation)
Java深度历险(六)——Java注解
公共技术点之 Java 注解 Annotation