我的Java Web之路 - Java 注解(1)

介绍

前面的文章介绍了如何基于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注解的代码时针对该注解的解释和处理可以添加某些功能逻辑。这就是标记本身存在的意义,毕竟给某些数据打上标记,当然就是为了针对该标记进行某种解释和处理,你可以用来检测,可以用来配置等等。

但是打标记也只是元数据的其中一种方式而已,你当然可以在代码之外单独的对代码进行描述,比如广泛使用的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=

我们可以把注解简单的理解为一个特殊的接口,那么注解的使用也就可以简单的理解为:

  1. 定义了一个实现该接口的类;
  2. 调用了该类的构造方法生成了一个对象。

至少从使用形式上看是差不多的。

Java注解的解释和处理

由于Java注解的解释和处理用到了Java反射技术,我们以后再讨论。

总结

好,现在我们明白Java注解是什么,起什么作用,能用来干什么了。大多数情况下,我们只要会使用别人提供的注解就行了。如果非要为自己开发的程序提供注解,那么通常都有两项任务:

  • 定义注解;
  • 编写注解的解释和处理逻辑。

下面是一些总结:

  • 注解也是源于打标记的思想,即元数据
  • 不同于xml,Java注解与代码是紧耦合的,实际上Java注解的修改也必须要重新编译,所以必须考虑什么场景比较适合用注解,什么场景适合用xml;
  • Java注解提供了一种标准的定义元数据的方式;
  • Java注解的定义使用@interface
  • Java注解的定义是不包含任何业务逻辑的,只是定义了一个标记名称及其拥有的属性,仅此而已;
  • Java注解的定义和使用都可以拿接口来类比,形式上很像。
  • 任何事物都有生命周期,Java注解也一样,它有三种生命周期,分别是源码级别、字节码级别、运行时级别。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值