本系列文章旨在记录和总结自己在Java Web开发之路上的知识点、经验、问题和思考,原来已经分享在我的CSDN博客,现在分享在头条,希望能帮助更多 码农 和想成为码农的人。版权声明:本文为CSDN博主「普通的码农」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。原文链接:
目录
- 介绍
- 注解的思想、原理、本质
- 为什么要引入 Java 注解
- Java注解的定义
- Java注解的使用
- Java注解的解释和处理
- 总结
介绍
介绍了如何基于 servlet 技术建立我们的第一个Java Web应用,并且通过阅读Tomcat提供的(Servlet规范的一个实现)源码,了解到 抽象类 、 接口 和 多态 等Java基本概念。
但是,还有一个很重要的技术我们还没介绍,这就是在配置我们开发的Servlet时用到的 注解( Annotation ) ,这里再次把我们的第一个Java Web应用的源码展示出来:
package com.example; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.WebServlet; @WebServlet(urlPatterns = {"/hello"}) //这就是注解 public class HelloWorld extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContent TYPE ("text/ html "); PrintWriter writer = response.getWriter(); writer.print("<html><head></head><body>" + "<h1>Hello World! Your IP is " + request.getRemoteHost() + "</h1>" + "</body></html>"); } }
代码中的 @WebServlet 就是一个注解,本篇我们就介绍 Java注解 。
注解的思想、原理、本质
本质上,Java注解技术也是源于 打标记 的思想。
还记得我们之前介绍过的也是基于打标记思想的技术吗?就是 网页HTML 技术。所以,它们是有异曲同工之妙的。不过,也是完全不一样的技术,我们可以从以下方面来进行比较:
- 标记的定义 :HTML的标记是由相应的标准组织定义和扩展的;Java注解可以由用户自己定义,而Java语言本身定义了大量注解,第三方库也提供了大量的与库本身相关的注解。
- 标记的作用目标 :HTML的标记作用在要呈现给用户的信息上;Java注解作用在Java代码上。
- 标记的解释和处理 :HTML的标记是由浏览器来解释和处理;Java注解就比较复杂且强大多了,它其实说是由其他工具或代码来解释和处理,比如Java编译器可以解释和处理某些注解,第三方工具解释和处理自己提供的注解(比如Tomcat解释并处理@WebServlet),用户可以自己开发代码来解释自己定义的注解。
所谓标记,用专业一点的术语来说就是 元数据 ,即描述数据的数据。
HTML中,要呈现给用户的信息是数据,HTML标签就是描述它们的数据;
Java代码(类、属性域、方法、参数等等)也是一种数据,Java注解就是描述它们的数据。
为什么要引入Java注解
Java注解其实是在Java 1.5版本(或者说是Java 5)引入的,那么为什么要引入这种技术呢?或者说引入Java注解是为了解决什么问题呢?
从Java注解的本质上看,它是一种标记,那么我们就可以针对这个标记进行解释和处理,从而达到对Java代码在 编译时 进行检测,甚至在Java程序 运行时 执行到有Java注解的代码时针对该注解的解释和处理可以添加某些功能逻辑。
这就是标记本身存在的意义,毕竟给某些数据打上标记,当然就是为了针对该标记进行某种解释和处理,你可以用来检测,可以用来配置等等。
但是打标记也只是元数据的其中一种方式而已,你当然可以在代码之外单独的对代码进行描述,比如广泛使用的xml,它广泛使用在Java程序的配置上。既然xml这种技术也可以实现元数据的思想,那么为什么Java还要引入注解技术呢?
答案在于xml技术是 基于代码与配置分离(即松耦合)原则 ,而有时候我们却又希望使用一些 与代码紧耦合 的东西,这样 更方便更易于维护 ,所以Java注解应运而生。这两种方式各有利弊,各有自己的使用场合。
另外,在Java注解之前,描述元数据的方式不仅限于xml,还有 标记接口 、 注释 、 transient 关键字等等(这些暂不讨论),因此处于一种混乱不堪的状态,我们需要 一种标准的方式 来描述元数据,这就是Java注解。
Java注解的定义
前面提到,Java注解是由我们用户来定义的,就是说谁都可以定义Java注解。事实上,Java注解就跟Java类和接口一样,谁都可以定义,它也需要 抽象 出来。
我们先来看看@WebServlet是怎样定义的,与 一样,可以查看它的源码:
package javax.servlet.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface WebServlet { String name() default ""; String[] value() default {}; String[] urlPatterns() default {}; int loadOnStartup() default -1; WebInitParam[] initParams() default {}; boolean asyncSupported() default false; String smallIcon() default ""; String largeIcon() default ""; String description() default ""; String displayName() default ""; }
我在这省略了大量的注释。
首先,我们可以看到定义注解的关键字是 @interface 是在接口关键字前面加上一个@符号,由此可见注解与接口是多么的相似。注解的名称、修饰符与定义类和接口时遵从的规范是一样的。
然后,我们看注解体。既然跟接口相似,那么里面的内容也就差不多,都是一些方法。不过,这些方法的方法名明显都是 名词 ,实际上这是定义注解的 属性 ,比如WebServlet这个注解有以下这个方法:
String[] urlPatterns() default {};
所以,在使用WebServlet这个注解时,可以指定urlPatterns这个属性,它的属性值是字符串数组类型。当然,后面指定了它的默认值是一个空数组。所以,可以像下面的方式那样使用这个注解:
@WebServlet(urlPatterns = {"/hello"})
Java语言规定,注解体中的方法返回类型只能是:int等基本数据类型、String、 enum (暂且不讨论)、其他注解(比如上面的WebInitParam其实也是一个注解),以及它们对应的数组。
最后,我们来看看WebServlet上面的内容:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented
很明显,它们也是注解,从import语句可以看出它们来自于Java语言(就是JDK库)。它们其实可以叫做 元注解 ,就是专门注解其他注解的,感觉有点绕。Java提供了四个元注解:
- @Target :这就是前面提到的 标记的目标 ,即指定你所定义注解的作用目标,大的方面是Java代码,但这没什么意义,更细的目标应该是类、接口、属性域、方法、 构造方法 、参数等等,这个我们可以继续通过查看 ElementType 的 JavaDoc 或者源码看到。
- @Retention :指定你所定义注解的生命周期,即 注解起作用的时间 ,我们同样可以通过看 RetentionPolicy 的源码,得知注解的生命周期有:
- SOURCE :源码阶段,即在编译结束之后就不再有任何意义,所以它们不会写入 字节码 。
- CLASS :字节码阶段,在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式。
- RUNTIME :运行时阶段,始终不会丢弃,运行期也保留该注解,因此可以使用 反射机制 读取该注解的信息。我们自定义的注解通常使用这种方式。
- @Documented :指定你所定义的注解是否包含在某个目标(当然该目标使用了你所定义的注解)的JavaDoc中。
- @Inherited :指定你所定义的注解是否允许子类继承。
自定义注解时,@Target和@Retention通常都会指定,大体就是这样:
@Target( [ElementType.TYPE] ) @Retention( [RetentionPolicy.RUNTIME] ) [public] @interface [annotationName] { [String] [fieldName]() [default ""]; //其他属性省略 }
方括号中的都是用户可以自己修改的。
最后,从注解的定义可以看到,它感觉就像是接口一样,不包含任何业务逻辑、功能逻辑,它就只是定义了一个标记而已,这个标记有一些属性。
那么必须有人来实现这些逻辑,这些逻辑就是能够读取并识别这些注解,能够获取到使用注解时赋予属性的值,然后根据它们来执行某些功能。这就是 注解的解释和处理 。
Java注解的使用
HTML标签的使用是把标记名称放在 尖括号 中,而且有的还有结束标签。
Java注解的使用是在注解名称加上 @符号 ,然后使用小括号,在小括号中为注解的属性赋值即可。比如:
@WebServlet(urlPatterns = {"/hello"})
当然,注解必须使用在指定的目标类型上,如果定义注解时指定目标是ElementType.TYPE,那么它就只能用在类、接口(包括注解)、枚举(enum)上;
如果是ElementType.FIELD,那么它就只能用在类的属性域(包括枚举常量)上;等等。
剩下的可以我们可以自己看JavaDoc或源码。
从WebServlet注解的使用可以看出,为注解的名称和属性起一个恰当的名字也是相当重要的。
另外,如果注解的属性只有一个且名称为value的时候,使用时可以省略value=。
我们可以把注解简单的理解为一个特殊的接口,那么注解的使用也就可以简单的理解为:
- 定义了一个实现该接口的类;
- 调用了该类的构造方法生成了一个对象。
至少从使用形式上看是差不多的。
Java注解的解释和处理
由于Java注解的解释和处理用到了Java反射技术,我们以后再讨论。
总结
好,现在我们明白Java注解是什么,起什么作用,能用来干什么了。大多数情况下,我们只要会使用别人提供的注解就行了。如果非要为自己开发的程序提供注解,那么通常都有两项任务:
- 定义注解;
- 编写注解的解释和处理逻辑。
下面是一些总结:
- 注解也是源于 打标记 的思想,即 元数据 ;
- 不同于xml, Java注解与代码是紧耦合的 ,实际上Java注解的修改也必须要重新编译,所以必须考虑什么场景比较适合用注解,什么场景适合用xml;
- Java注解提供了一种 标准的定义元数据的方式 ;
- Java注解的定义使用 @interface ;
- Java注解的定义是不包含任何业务逻辑的,只是定义了一个标记名称及其拥有的属性,仅此而已;
- Java注解的定义和使用都可以拿 接口 来类比,形式上很像。
- 任何事物都有 生命周期 ,Java注解也一样,它有三种生命周期,分别是源码级别、字节码级别、运行时级别。