关于注解的初步探究

前言

  之前在学习Java基础时,为了提高学习效率,早点上手做东西,就刻意的跳过了当时以为应用频率不是很高的章节知识,其中就包括多线程、反射、注解、GUI、socket等。前些日子花了两周多的时间把目前Web开发中较为流行的Spring、SpringMVC、SpringBoot、Mybatis框架大致过了一遍,知道了其基本运行原理和相关的基础知识。在研究这些框架的源码时,我发现其中充斥着大量我之前闻所未闻的注解,看得我一头雾水。诚然,不去研究这些注解背后实现的原理,而只去了解这些注解有什么用以及在什么时候用也可以完成基本的开发,但是,这些建立在“黑箱”之上的所谓开发在我看来无异于空中楼阁,稍微出了点什么问题自己不会处理,整个项目便会轰然倒塌,自己开发时心也不稳。所以,授人以鱼不如授人以渔,只有掌握了基本原理,方能举一反三,使自己的学习取得长远性的进步。

什么是注解

  The common interface extended by all annotation types.

  这是java.lang.annotation.Annotation接口中用来描述注解的一句话,意思即所有的注解都继承自Annotation这个普通的接口。这句话其实说出了注解的本质:注解就是一个继承了Annotation接口的接口。

1 @Target(ElementType.METHOD)
2 @Retention(RetentionPolicy.SOURCE)
3 public @interface Override {
4 5 }

  这是一个JDK内置的override注解的定义。,它本质上就是

 1 public interface Override extends Annotation{ 2 3 } 

  所以,再准确点说,注解其实就是一种特殊的注释,一种写给编译器看的注释,好让编译器看到这个注解后知晓程序是干什么的以及改在什么时候被执行。

为什么需要注解

  知道了注解的确切定义后,我不禁开始思考起另一个问题:Why?为什么需要注解?

  翻看了下文档后,我了解到注解其实是紫Java5.0后才开始被引入的新内容。在我看来,从某种意义上来讲,Java每次更新版本后引入新内容最大的作用无非两个:简化开发,提高开发效率;修补漏洞,提高语言的安全性和严谨性。当然,以上也只是我自己较为狭隘的认知,毕竟我也只是个菜鸟而已。所以,基于这个论点,我们来看看注解究竟在开发中扮演着一个怎样的角色。

  在Web发展的初期,并没有什么乱七八糟的开发框架,大家都是用最原始的方法来开发构建一个Web系统(我现在还是在用Jsp+servlet,导致面试时总是被嘲笑,所以才痛定思痛,决定把框架好好的学习掌握一下......)。以Jsp+Servlet构建的项目为例,当我们自己写好了servlet后,必须要去web.xml中注册,这样系统在运行时才会知道这个你所写的这个类是一个servlet,有什么作用以及在什么条件下会被执行

1 <servlet> 
2     <servlet-name>ManagerServlet</servlet-name>
3     <servlet-class>com.partner.controller.ManagerServlet</servlet-class> </servlet>
4 
5 <servlet-mapping>
6     <servlet-name>ManagerServlet</servlet-name> 
7     <url-pattern>/servlet/ManagerServlet</url-pattern> 
8 </servlet-mapping

  如上图所示,每个自定义的servlet都需要在web.xml中注册。这样看起来虽然一目了然,但是当项目变得巨大无比时,光是配置servlet的代码都得好多行,显得无比臃肿。对于后期的维护什么的,更是一种痛苦的负担。人都是懒的,框架的出现在我看来其实就是满足了人们对于 懒 的需求。如果不是为了简化开发步骤,缩短开发时间,我自己用最原始的方法照样可以完成一个Web项目,为什么还要使用框架呢?我还得去了解其中的各种约定和使用说明......

  所以,为了简化书写,我们决定引入一个@WebServlet注解,在自己实现了HttpServlet接口的类上面加上这个注解后,我们便无需再往web.xml配置这个servlet,由于这个注解的存在,系统自动便会知道这个类是一个servlet,会和servlet中对应的各种规范映射起来,这个servlet也会在特定的条件下被执行。这样一来,我们用一个简洁的@WebServlet,便取代了上面那一大坨乱七八糟的手工繁琐配置,注解的优越性体现的淋漓尽致。

  当然,注解是一个强大的功能,它的用处当然不限于此,但是在此我也就不多说了,等以后我有新的体会了再来更新,目前我就狭隘的认为注解就是为了简化开发的就行。

再次思考

  经过以上的分析后,我又开始思考起另一个问题:注解是怎么其作用的呢?为什么我写了一个@WebServlet的注释给编译器后,它就能知道这个类是干嘛的呢?

要想解释这一切行为背后的原理,我们不得不再说到另一个关键的知识点:反射。

  注解的执行原理还没有搞清楚呢,又整出来个听起来花里花哨的反射,真是头大。可是没办法,要想真正的把问题搞清楚,可不是三两句话就能搞定的,硬着头皮接着研究吧!

何为反射

  要弄清楚反射究竟是什么这个问题之前,我们不妨先看一下反射的具体定义:Java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取获取信息以及动态调用对象的方法的功能被称为Java语言的反射机制。简单来讲,反射就是把Java的.class文件的各种成分给映射成一个个Java对象。计算机网络中的ARP协议能提供IP到MAC的映射,而RARP协议则恰恰反向,做的是MAC到IP的映射,反射实现的功能也可以类比为RARP。通过反射,JVM可以在拿到字节码文件的基础上,对类进行“庖丁解牛”般的操作,实现自己的需求。至于反射的具体实现原理,我后续再开一篇文章来详细讲。

注解详解及执行过程

  以SpringBoot框架(以下简称SB)中特别常用的@RestController注解为例,先来看下它的源码

 1 @Target({ElementType.TYPE})
 2 @Retention(RetentionPolicy.RUNTIME)
 3 @Documented
 4 @Controller
 5 @ResponseBody
 6 public @interface RestController {
 7     @AliasFor(
 8         annotation = Controller.class
 9     )
10     String value() default "";
11 }

  我们来看下它每行代码的详细含义:

  @Target:是Java的四个元注解之一。元注解,即注解的注解,用来修饰自定义注解。此注解用来定义你的注解将应用于什么地方,也就是作用域的问题。可能的值被定义在枚举类ElementType中,我们来看一下ElementType的源码

 1 public enum ElementType {
 2     /** Class, interface (including annotation type), or enum declaration 
 3         应用于类,接口,注解,枚举*/
 4     TYPE,
 5  6     /** Field declaration (includes enum constants) 应用于全局属性*/
 7     FIELD,
 8  9     /** Method declaration 应用于方法*/
10     METHOD,
11 12     /** Formal parameter declaration 应用于方法的参数*/
13     PARAMETER,
14 15     /** Constructor declaration 应用于构造器*/
16     CONSTRUCTOR,
17 18     /** Local variable declaration 应用于本地变量*/
19     LOCAL_VARIABLE,
20 21     /** Annotation type declaration 应用于其它注解的元注解*/
22     ANNOTATION_TYPE,
23 24     /** Package declaration 应用于包*/
25     PACKAGE,
26 27     /**
28      * Type parameter declaration
29      *  用来标注类型参数
30      * @since 1.8
31      */
32     TYPE_PARAMETER,
33 34     /**
35      * Use of a type
36      *  能标注任何类型
37      * @since 1.8
38      */
39     TYPE_USE,
40 41     /**
42      * Module declaration.
43      *
44      * @since 9
45      */
46     MODULE
47 }

  而这里则表示改注解可以用来标注类、接口、注解或者枚举类型

  @Retention:是Java的四个元注解之一。用来指定被修饰的自定义注解可以保存多久,看下RetentionPolicy的源码

 1 public enum RetentionPolicy {
 2     /**
 3      * Annotations are to be discarded by the compiler.
 4      * 编译器将直接丢弃被修饰的注解,即该注解只在编码阶段起作用
 5      */
 6     SOURCE,
 7  8     /**
 9      * Annotations are to be recorded in the class file by the compiler
10      * but need not be retained by the VM at run time.  This is the default
11      * behavior.
12      * 默认值,编译器将把注解记录在.class文件中,当运行Java程序时,虚拟机将不再保留注解
13      * 该注解在编译阶段起作用,运行时被丢弃
14      */
15     CLASS,
16 17     /**
18      * Annotations are to be recorded in the class file by the compiler and
19      * retained by the VM at run time, so they may be read reflectively.
20      * 编译器把注解记录在.class文件中,当运行Java程序时,虚拟机将保留注解,程序可以通过反射获取该注解
21      * 该注解在运行阶段一直保留
22      * @see java.lang.reflect.AnnotatedElement
23      */
24     RUNTIME
25 }

  此处表示该注解在运行时可以通过反射获取到(体现了反射的重要之处)。

  @Documented:是Java的四个元注解之一。执行javadoc命令时,被该元注解修饰的自定义注解也会生成在文档中。

  @Controller:此注解是Spring框架定义的一个注解,使用它标记的类就是一个SpringMVC Controller对象,分发处理器会扫描使用该注解的类的方法,并检测该方法是否使用了@RequestMapping注解。在这里我们只需要把它理解为此注解标注了该类为一个控制器。

  @ResponseBody:该注解的作用是将方法的返回值以特定的格式写入到response的body区域,进而将数据返回给客户端,通常用来返回json数据或者xml数据。

  @AliasFor:顾名思义,@AliasFor表示别名。它可以注解到自定义注解的两个属性上,表示这两个互为别名,也就是说这两个属性其实是一样的,表示同一个含义(这个点感觉值得探究一下,它的用处貌似远不止这两句话说的那么简单)。

  注解部分分析完毕,我们再来看一下那仅有的一行代码:

  String value() default "":这行代码表示该注解中有一个名字为value的可选参数,不指定的话默认为“ ”。

  分析完毕,我们可以知道,当带有注解信息的字节码文件被JVM运行时,JVM会通过JAVA的反射机制动态生成相应的对象,然后根据注解信息,分别作出不同的对应操作,这就是注解从定义到被执行的详细过程。

posted @ 2020-06-15 09:47  MajorTom、  阅读(152)  评论(0)    收藏  举报