本系列文章旨在记录和总结自己在Java Web开发之路上的知识点、经验、问题和思考,原来已经分享在我的CSDN博客,现在分享在头条,希望能帮助更多码农和想成为码农的人。版权声明:本文为CSDN博主「普通的码农」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/liyongyan1202/article/details/89185622
目录
- 介绍
- 注解的思想、原理、本质
- 为什么要引入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.setContentType("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是怎样定义的,与查看HttpServlet类的源码一样,可以查看它的源码:
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注解也一样,它有三种生命周期,分别是源码级别、字节码级别、运行时级别。