Java 注解

                           Java 注解

Java 中的注解是如何工作的?

自 Java5.0 版本引入注解之后,它就成为了 Java 平台中非常重要的一部分。开发过程中,我们也时常在应用代码中会看到诸如 @Override,@Deprecated 这样的注解。这篇文章中,我将向大家讲述到底什么是注解,为什么要引入注解,注解是如何工作的,如何编写自定义的注解 (通过例子),什么情况下可以使用注解以及最新注解和 ADF(应用开发框架)。这会花点儿时间,所以为自己准备一杯咖啡,让我们来进入注解的世界吧。

什么是注解?

用一个词就可以描述注解,那就是元数据,即一种描述数据的数据。所以,可以说注解就是源代码的元数据。比如,下面这段代码:

1
2
3
4
@Override
public String toString() {
    return "This is String Representation of current object.";
}

上面的代码中,我重写了toString()方法并使用了@Override注解。但是,即使我不使用@Override注解标记代码,程序也能够正常执行。那么,该注解表示什么?这么写有什么好处吗?事实上,@Override告诉编译器这个方法是一个重写方法(描述方法的元数据),如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。如果我不小心拼写错误,例如将toString()写成了toStrring(){double r},而且我也没有使用@Override注解,那程序依然能编译运行。但运行结果会和我期望的大不相同。现在我们了解了什么是注解,并且使用注解有助于阅读程序。

Annotation 是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由 JSR-175 标准选择用来描述元数据的一种工具。

为什么要引入注解?

使用 Annotation 之前 (甚至在使用之后),XML 被广泛的应用于描述元数据。不知何时开始一些应用开发人员和架构师发现 XML 的维护越来越糟糕了。他们希望使用一些和代码紧耦合的东西,而不是像 XML 那样和代码是松耦合的(在某些情况下甚至是完全分离的) 代码描述。如果你在 Google 中搜索“XML vs. annotations”,会看到许多关于这个问题的辩论。最有趣的是 XML 配置其实就是为了分离代码和配置而引入的。上述两种观点可能会让你很疑惑,两者观点似乎构成了一种循环,但各有利弊。下面我们通过一个例子来理解这两者的区别。

假如你想为应用设置很多的常量或参数,这种情况下,XML 是一个很好的选择,因为它不会同特定的代码相连。如果你想把某个方法声明为服务,那么使用 Annotation 会更好一些,因为这种情况下需要注解和方法紧密耦合起来,开发人员也必须认识到这点。

另一个很重要的因素是 Annotation 定义了一种标准的描述元数据的方式。在这之前,开发人员通常使用他们自己的方式定义元数据。例如,使用标记 interfaces,注释,transient 关键字等等。每个程序员按照自己的方式定义元数据,而不像 Annotation 这种标准的方式。

目前,许多框架将 XML 和 Annotation 两种方式结合使用,平衡两者之间的利弊。

Annotation 是如何工作的?怎么编写自定义的 Annotation?

在讲述这部分之前,建议你首先下载 Annotation 的示例代码AnnotationsSample.zip 。下载之后放在你习惯使用的 IDE 中,这些代码会帮助你更好的理解 Annotation 机制。

编写 Annotation 非常简单,可以将 Annotation 的定义同接口的定义进行比较。我们来看两个例子:一个是标准的注解 @Override,另一个是用户自定义注解 @Todo。

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

对于 @Override 注释你可能有些疑问,它什么都没做,那它是如何检查在父类中有一个同名的函数呢。当然,不要惊讶,我是逗你玩的。@Override 注解的定义不仅仅只有这么一点代码。这部分内容很重要,我不得不再次重复:Annotations 仅仅是元数据,和业务逻辑无关。理解起来有点困难,但就是这样。如果 Annotations 不包含业务逻辑,那么必须有人来实现这些逻辑。元数据的用户来做这个事情。Annotations 仅仅提供它定义的属性 (类 / 方法 / 包 / 域) 的信息。Annotations 的用户 (同样是一些代码) 来读取这些信息并实现必要的逻辑。

当我们使用 Java 的标注 Annotations(例如 @Override) 时,JVM 就是一个用户,它在字节码层面工作。到这里,应用开发人员还不能控制也不能使用自定义的注解。因此,我们讲解一下如何编写自定义的 Annotations。

我们来逐个讲述编写自定义 Annotations 的要点。上面的例子中,你看到一些注解应用在注解上。

J2SE5.0 版本在 java.lang.annotation 提供了四种元注解,专门注解其他的注解:

@Documented –注解是否将包含在JavaDoc中
@Retention –什么时候使用该注解
@Target? –注解用于什么地方
@Inherited – 是否允许子类继承该注解

@Documented–一个简单的 Annotations 标记注解,表示是否将注解信息添加在 java 文档中。

@Retention– 定义该注解的生命周期。

RetentionPolicy.SOURCE – 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings 都属于这类注解。

RetentionPolicy.CLASS – 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式。

RetentionPolicy.RUNTIME– 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

@Target – 表示该注解用于什么地方。如果不明确指出,该注解可以放在任何地方。以下是一些可用的参数。需要说明的是:属性的注解是兼容的,如果你想给 7 个属性都添加注解,仅仅排除一个属性,那么你需要在定义 target 包含所有的属性。

ElementType.TYPE:用于描述类、接口或enum声明
ElementType.FIELD:用于描述实例变量
ElementType.METHOD
ElementType.PARAMETER
ElementType.CONSTRUCTOR
ElementType.LOCAL_VARIABLE
ElementType.ANNOTATION_TYPE 另一个注释
ElementType.PACKAGE 用于记录java文件的package信息

@Inherited – 定义该注释和子类的关系

那么,注解的内部到底是如何定义的呢?Annotations 只支持基本类型、String 及枚举类型。注释中所有的属性被定义成方法,并允许提供默认值。

1
2
3
4
5
6
7
8
9
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Todo {
public enum Priority {LOW, MEDIUM, HIGH}
public enum Status {STARTED, NOT_STARTED}
String author() default "Yash";
Priority priority() default Priority.LOW;
Status status() default Status.NOT_STARTED;
}

下面的例子演示了如何使用上面的注解。

1
2
3
4
5
@Todo(priority = Todo.Priority.MEDIUM, author = "Yashwant", status = Todo.Status.STARTED)
public void incompleteMethod1() {
//Some business logic is written
//But it’s not complete yet
}

如果注解中只有一个属性,可以直接命名为“value”,使用时无需再标明属性名。

1
2
3
4
5
6
@interface Author{
String value();
}
@Author("Yashwant")
public void someMethod() {
}

但目前为止一切看起来都还不错。我们定义了自己的注解并将其应用在业务逻辑的方法上。现在我们需要写一个用户程序调用我们的注解。这里我们需要使用反射机制。如果你熟悉反射代码,就会知道反射可以提供类名、方法和实例变量对象。所有这些对象都有 getAnnotation() 这个方法用来返回注解信息。我们需要把这个对象转换为我们自定义的注释 ( 使用 instanceOf()检查之后),同时也可以调用自定义注释里面的方法。看看以下的实例代码,使用了上面的注解:

1
2
3
4
5
6
7
8
9
10
Class businessLogicClass = BusinessLogic.class;
for(Method method : businessLogicClass.getMethods()) {
Todo todoAnnotation = (Todo)method.getAnnotation(Todo.class);
if(todoAnnotation != null) {
System.out.println(" Method Name : " + method.getName());
System.out.println(" Author : " + todoAnnotation.author());
System.out.println(" Priority : " + todoAnnotation.priority());
System.out.println(" Status : " + todoAnnotation.status());
}
}

注解用例

注解的功能很强大,Spring 和 Hebernate 这些框架在日志和有效性中大量使用了注解功能。注解可以应用在使用标记接口的地方。不同的是标记接口用来定义完整的类,但你可以为单个的方法定义注释,例如是否将一个方法暴露为服务。

在最新的 servlet3.0 中引入了很多新的注解,尤其是和 servlet 安全相关的注解。

HandlesTypes –该注解用来表示一组传递给 ServletContainerInitializer 的应用类。

HttpConstraint – 该注解代表所有 HTTP 方法的应用请求的安全约束,和 ServletSecurity 注释中定义的 HttpMethodConstraint 安全约束不同。

HttpMethodConstraint – 指明不同类型请求的安全约束,和 ServletSecurity 注解中描述 HTTP 协议方法类型的注释不同。

MultipartConfig –该注解标注在 Servlet 上面,表示该 Servlet 希望处理的请求的 MIME 类型是 multipart/form-data。

ServletSecurity 该注解标注在 Servlet 继承类上面,强制该 HTTP 协议请求遵循安全约束。

WebFilter – 该注解用来声明一个 Server 过滤器;

WebInitParam – 该注解用来声明 Servlet 或是过滤器的中的初始化参数,通常配合 @WebServlet 或者 @WebFilter 使用。

WebListener –该注解为 Web 应用程序上下文中不同类型的事件声明监听器。

WebServlet –该注解用来声明一个 Servlet 的配置。

ADF (应用程序框架) 和注解

现在我们开始讨论文章的最后一部分了。应用程序框架,被称为 ADF,由 Oracle 开发用来创建 Oracle 融合应用。我们已经了解了注解的优缺点,也知道如何编写自定义的注解,但我们应该将注解应用在 ADF 的哪部分呢?ADF 是否提供了一些朴素的注解?很好的问题,确实在 ADF 中大量使用注解有一些限制。之前提到的应用框架如 Spring 和 Hibernate 使用 AOP(面向侧面的程序设计)。在 AOP 中,框架提供了一种机制,在事件的预处理和后续处理中注入代码。例如:你有一个钩子用来在方法执行之前和之后添加代码,所以你可以在这些地方编写你的用户代码。ADF 不使用 AOP。如果我们有任何注解的用例可用,我们可能需要通过继承的方式实现。

 

Java 注解

编辑
注解基础知识点
定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是 JDK1.5 及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
作用分类:
①编写文档:通过代码里标识的元数据生成文档【生成文档 doc 文档】
② 代码分析:通过代码里标识的元数据对代码进行分析【使用反射】
③编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】
 
中文名
Java 注解
外文名
Annotation
别    名
元数据
含    义
一种代码级别的说明

JAVA 注解

编辑
Annotation(注解) 是 JDK1.5 及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。注解是以‘@注解名’在代码中存在的,根据注解参数的个数,我们可以将注解分为:标记注解、单值注解、完整注解三类。它们都不会直接影响到程序的语义,只是作为注解(标识)存在,我们可以通过反射机制编程实现对这些元数据(用来描述数据的数据)的访问。另外,你可以在编译时选择代码里的注解是否只存在于源代码级,或者它也能在 class 文件、或者运行时中出现(SOURCE/CLASS/RUNTIME)。

元数据的作用

编辑
如果要对于元数据的作用进行分类,还没有明确的定义,不过我们可以根据它所起的作用,大致可分为三类:
编写文档:通过代码里标识的元数据生成文档。
代码分析:通过代码里标识的元数据对代码进行分析。
编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查

元注解

编辑
[1] 

基本内置注解

@Override
它的作用是对覆盖超类中方法的方法进行标记,如果被标记的方法并没有实际覆盖超类中的方法,则编译器会发出错误警告。
1
2
3
4
5
6
7
8
9
10
11
Java代码
/**
 * 测试Override注解
 * @author Administrator
 */
public class OverrideDemoTest {
    //@Override
    public String tostring() {
        return "测试注解";
    }
}
@Deprecated
它的作用是对不应该再使用的方法添加注解,当编程人员使用这些方法时,将会在编译时显示提示信息,它与 javadoc 里的 @deprecated 标记有相同的功能,准确的说,它还不如 javadoc @deprecated,因为它不支持参数,使用 @Deprecated 的示例代码示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Java代码
/**
 * 测试Deprecated注解
 * @author Administrator
 */
public class DeprecatedDemoTest {
    public static void main(String[]args) {
        // 使用DeprecatedClass里声明被过时的方法
        DeprecatedClass.DeprecatedMethod();
    }
}
 
class DeprecatedClass {
    @Deprecated
    public static void DeprecatedMethod() {
    }
}
@SuppressWarnings
其参数有:
deprecation,使用了过时的类或方法时的警告
unchecked,执行了未检查的转换时的警告
fallthrough,当 switch 程序块直接通往下一种情况而没有 break 时的警告
path,在类路径、源文件路径等中有不存在的路径时的警告
serial,当在可序列化的类上缺少 serialVersionUID 定义时的警告
finally ,任何 finally 子句不能正常完成时的警告
all,关于以上所有情况的警告
1
2
3
4
5
6
7
8
9
10
11
Java代码
import java.util.ArrayList;
import java.util.List;
 
public class SuppressWarningsDemoTest {
    public static Listlist = newArrayList();
    @SuppressWarnings("unchecked")
    public void add(Stringdata) {
        list.add(data);
    }
}

自定义注解

它类似于新创建一个接口文件,但为了区分,我们需要将它声明为 @interface, 如下例:
1
2
3
Java代码
public @interface NewAnnotation {
}
使用自定义的注解类型
1
2
3
4
5
6
Java代码
public class AnnotationTest {
    @NewAnnotation
    public static void main(String[]args) {
    }
}
为自定义注解添加变量
1
2
3
4
Java代码
public @interface NewAnnotation {
    String value();
}
1
2
3
4
5
6
7
8
9
10
11
Java代码
public class AnnotationTest {
    @NewAnnotation("mainmethod")
    public static void main(String[]args) {
        saying();
    }
     
    @NewAnnotation(value="saymethod")
    public static void saying() {
    }
}
定义一个枚举类型,然后将参数设置为该枚举类型,并赋予默认值
1
2
3
4
5
6
7
8
9
public @interface Greeting {
    public enum FontColor {
        BLUE,
        RED,
        GREEN
    };
    String name();
    FontColor fontColor() default FontColor.RED;
}
这里有两种选择,其实变数也就是在赋予默认值的参数上,我们可以选择使用该默认值,也可以重新设置一个值来替换默认值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Java代码
public class AnnotationTest {
    @NewAnnotation("mainmethod")
    public static void main(String[]args) {
        saying();
        sayHelloWithDefaultFontColor();
        sayHelloWithRedFontColor();
    }
 
    @NewAnnotation("saymethod")
    public static void saying() {
    }
     
    // 此时的fontColor为默认的RED
    @Greeting(name="defaultfontcolor")
    public static void sayHelloWithDefaultFontColor() {
    }
 
    // 将fontColor改为BLUE
    @Greeting(name="notdefault", fontColor=Greeting.FontColor.BLUE)
    public static void sayHelloWithRedFontColor() {
    }
}

注解的高级应用

编辑

限制注解的使用范围

用 @Target 指定 ElementType 属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Java代码(jdk)
public enum ElementType {
    // 用于类,接口,枚举但不能是注解
    TYPE,
    // 字段上,包括枚举值
    FIELD,
    // 方法,不包括构造方法
    METHOD,
    // 方法的参数
    PARAMETER,
    // 构造方法
    CONSTRUCTOR,
    // 本地变量或catch语句
    LOCAL_VARIABLE,
    // 注解类型(无数据)
    ANNOTATION_TYPE,
    // Java包
    PACKAGE
}
具体例子:
1
2
3
4
5
6
7
8
9
10
11
Java代码
// 限制注解使用范围
@Target({ElementType.METHOD,ElementType.CONSTRUCTOR})
public @interface Greeting {
    // 使用枚举类型
    public enum FontColor {
        BLUE,RED,GREEN
    };
    String name();
    FontColor fontColor() default FontColor.RED;
}

注解保持性策略

在 Java编译器编译时,它会识别在源代码里添加的注解是否还会保留,这就是 RetentionPolicy。下面是 Java 定义的 RetentionPolicy 枚举:
编译器的处理有三种策略:
将注解保留在编译后的类文件中,并在第一次加载类时读取它;
将注解保留在编译后的类文件中,但是在运行时忽略它;
按照规定使用注解,但是并不将它保留到编译后的类文件中。
1
2
3
4
5
6
7
8
9
Java代码
public enum RetentionPolicy {
    // 此类型会被编译器丢弃
    SOURCE,
    // 此类型注解会保留在class文件中,但JVM会忽略它
    CLASS,
    // 此类型注解会保留在class文件中,JVM会读取它
    RUNTIME
}
1
2
3
4
5
6
7
8
9
10
11
Java代码
// 让保持性策略为运行时态,即将注解编码到class文件中,让虚拟机读取
@Retention(RetentionPolicy.RUNTIME)
public @interface Greeting {
    // 使用枚举类型
    public enum FontColor {
        BLUE,RED,GREEN
    };
    String name();
    FontColor fontColor() default FontColor.RED;
}

文档化功能

Java 提供的 Documented 元注解跟 Javadoc 的作用是差不多的,其实它存在的好处是开发人员可以定制 Javadoc 不支持的文档属性,并在开发中应用。它的使用跟前两个也是一样的,简单代码示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
Java代码
// 让它定制文档化功能
// 使用此注解时必须设置RetentionPolicy为RUNTIME
@Documented
public @interface Greeting {
    // 使用枚举类型
    public enum FontColor {
        BLUE,RED,GREEN
    };
    String name();
    FontColor fontColor() default FontColor.RED;
}

标注继承

1
2
3
4
5
6
7
8
9
10
11
Java代码
// 让它允许继承,可作用到子类
@Inherited
public @interface Greeting {
    // 使用枚举类型
    public enum FontColor {
        BLUE,RED,GREEN
    };
    String name();
    FontColor fontColor() default FontColor.RED;
}

读取注解信息的方法

编辑
属于重点,在系统中用到注解权限时非常有用,可以精确控制权限的粒度
注意:要想使用反射去读取注解,必须将 Retention 的值选为 Runtime
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Java代码
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
 
//读取注解信息
public class ReadAnnotationInfoTest {
    public static void main(String[] args) throws Exception {
        // 测试AnnotationTest类,得到此类的类对象
        Class c = Class.forName("com.iwtxokhtd.annotation.AnnotationTest");
        // 获取该类所有声明的方法
        Method[] methods = c.getDeclaredMethods();
        // 声明注解集合
        Annotation[] annotations;
        // 遍历所有的方法得到各方法上面的注解信息
        for (Method method : methods) {
            // 获取每个方法上面所声明的所有注解信息
            annotations = method.getDeclaredAnnotations();
            // 再遍历所有的注解,打印其基本信息
            System.out.println(method.getName());
            for (Annotation an : annotations) {
                System.out.println("方法名为:" + method.getName() + "其上面的注解为:" + an.annotationType().getSimpleName());
                Method[] meths = an.annotationType().getDeclaredMethods();
                // 遍历每个注解的所有变量
                for (Method meth : meths) {
                    System.out.println("注解的变量名为:" + meth.getName());
                }
            }
        }
    }

Java 注解

编辑
注解基础知识点
定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是 JDK1.5 及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
作用分类:
①编写文档:通过代码里标识的元数据生成文档【生成文档 doc 文档】
② 代码分析:通过代码里标识的元数据对代码进行分析【使用反射】
③编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】
 
中文名
Java 注解
外文名
Annotation
别    名
元数据
含    义
一种代码级别的说明

JAVA 注解

编辑
Annotation(注解) 是 JDK1.5 及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。注解是以‘@注解名’在代码中存在的,根据注解参数的个数,我们可以将注解分为:标记注解、单值注解、完整注解三类。它们都不会直接影响到程序的语义,只是作为注解(标识)存在,我们可以通过反射机制编程实现对这些元数据(用来描述数据的数据)的访问。另外,你可以在编译时选择代码里的注解是否只存在于源代码级,或者它也能在 class 文件、或者运行时中出现(SOURCE/CLASS/RUNTIME)。

元数据的作用

编辑
如果要对于元数据的作用进行分类,还没有明确的定义,不过我们可以根据它所起的作用,大致可分为三类:
编写文档:通过代码里标识的元数据生成文档。
代码分析:通过代码里标识的元数据对代码进行分析。
编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查

元注解

编辑
[1] 

基本内置注解

@Override
它的作用是对覆盖超类中方法的方法进行标记,如果被标记的方法并没有实际覆盖超类中的方法,则编译器会发出错误警告。
1
2
3
4
5
6
7
8
9
10
11
Java代码
/**
 * 测试Override注解
 * @author Administrator
 */
public class OverrideDemoTest {
    //@Override
    public String tostring() {
        return "测试注解";
    }
}
@Deprecated
它的作用是对不应该再使用的方法添加注解,当编程人员使用这些方法时,将会在编译时显示提示信息,它与 javadoc 里的 @deprecated 标记有相同的功能,准确的说,它还不如 javadoc @deprecated,因为它不支持参数,使用 @Deprecated 的示例代码示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Java代码
/**
 * 测试Deprecated注解
 * @author Administrator
 */
public class DeprecatedDemoTest {
    public static void main(String[]args) {
        // 使用DeprecatedClass里声明被过时的方法
        DeprecatedClass.DeprecatedMethod();
    }
}
 
class DeprecatedClass {
    @Deprecated
    public static void DeprecatedMethod() {
    }
}
@SuppressWarnings
其参数有:
deprecation,使用了过时的类或方法时的警告
unchecked,执行了未检查的转换时的警告
fallthrough,当 switch 程序块直接通往下一种情况而没有 break 时的警告
path,在类路径、源文件路径等中有不存在的路径时的警告
serial,当在可序列化的类上缺少 serialVersionUID 定义时的警告
finally ,任何 finally 子句不能正常完成时的警告
all,关于以上所有情况的警告
1
2
3
4
5
6
7
8
9
10
11
Java代码
import java.util.ArrayList;
import java.util.List;
 
public class SuppressWarningsDemoTest {
    public static Listlist = newArrayList();
    @SuppressWarnings("unchecked")
    public void add(Stringdata) {
        list.add(data);
    }
}

自定义注解

它类似于新创建一个接口文件,但为了区分,我们需要将它声明为 @interface, 如下例:
1
2
3
Java代码
public @interface NewAnnotation {
}
使用自定义的注解类型
1
2
3
4
5
6
Java代码
public class AnnotationTest {
    @NewAnnotation
    public static void main(String[]args) {
    }
}
为自定义注解添加变量
1
2
3
4
Java代码
public @interface NewAnnotation {
    String value();
}
1
2
3
4
5
6
7
8
9
10
11
Java代码
public class AnnotationTest {
    @NewAnnotation("mainmethod")
    public static void main(String[]args) {
        saying();
    }
     
    @NewAnnotation(value="saymethod")
    public static void saying() {
    }
}
定义一个枚举类型,然后将参数设置为该枚举类型,并赋予默认值
1
2
3
4
5
6
7
8
9
public @interface Greeting {
    public enum FontColor {
        BLUE,
        RED,
        GREEN
    };
    String name();
    FontColor fontColor() default FontColor.RED;
}
这里有两种选择,其实变数也就是在赋予默认值的参数上,我们可以选择使用该默认值,也可以重新设置一个值来替换默认值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Java代码
public class AnnotationTest {
    @NewAnnotation("mainmethod")
    public static void main(String[]args) {
        saying();
        sayHelloWithDefaultFontColor();
        sayHelloWithRedFontColor();
    }
 
    @NewAnnotation("saymethod")
    public static void saying() {
    }
     
    // 此时的fontColor为默认的RED
    @Greeting(name="defaultfontcolor")
    public static void sayHelloWithDefaultFontColor() {
    }
 
    // 将fontColor改为BLUE
    @Greeting(name="notdefault", fontColor=Greeting.FontColor.BLUE)
    public static void sayHelloWithRedFontColor() {
    }
}

注解的高级应用

编辑

限制注解的使用范围

用 @Target 指定 ElementType 属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Java代码(jdk)
public enum ElementType {
    // 用于类,接口,枚举但不能是注解
    TYPE,
    // 字段上,包括枚举值
    FIELD,
    // 方法,不包括构造方法
    METHOD,
    // 方法的参数
    PARAMETER,
    // 构造方法
    CONSTRUCTOR,
    // 本地变量或catch语句
    LOCAL_VARIABLE,
    // 注解类型(无数据)
    ANNOTATION_TYPE,
    // Java包
    PACKAGE
}
具体例子:
1
2
3
4
5
6
7
8
9
10
11
Java代码
// 限制注解使用范围
@Target({ElementType.METHOD,ElementType.CONSTRUCTOR})
public @interface Greeting {
    // 使用枚举类型
    public enum FontColor {
        BLUE,RED,GREEN
    };
    String name();
    FontColor fontColor() default FontColor.RED;
}

注解保持性策略

在 Java编译器编译时,它会识别在源代码里添加的注解是否还会保留,这就是 RetentionPolicy。下面是 Java 定义的 RetentionPolicy 枚举:
编译器的处理有三种策略:
将注解保留在编译后的类文件中,并在第一次加载类时读取它;
将注解保留在编译后的类文件中,但是在运行时忽略它;
按照规定使用注解,但是并不将它保留到编译后的类文件中。
1
2
3
4
5
6
7
8
9
Java代码
public enum RetentionPolicy {
    // 此类型会被编译器丢弃
    SOURCE,
    // 此类型注解会保留在class文件中,但JVM会忽略它
    CLASS,
    // 此类型注解会保留在class文件中,JVM会读取它
    RUNTIME
}
1
2
3
4
5
6
7
8
9
10
11
Java代码
// 让保持性策略为运行时态,即将注解编码到class文件中,让虚拟机读取
@Retention(RetentionPolicy.RUNTIME)
public @interface Greeting {
    // 使用枚举类型
    public enum FontColor {
        BLUE,RED,GREEN
    };
    String name();
    FontColor fontColor() default FontColor.RED;
}

文档化功能

Java 提供的 Documented 元注解跟 Javadoc 的作用是差不多的,其实它存在的好处是开发人员可以定制 Javadoc 不支持的文档属性,并在开发中应用。它的使用跟前两个也是一样的,简单代码示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
Java代码
// 让它定制文档化功能
// 使用此注解时必须设置RetentionPolicy为RUNTIME
@Documented
public @interface Greeting {
    // 使用枚举类型
    public enum FontColor {
        BLUE,RED,GREEN
    };
    String name();
    FontColor fontColor() default FontColor.RED;
}

标注继承

1
2
3
4
5
6
7
8
9
10
11
Java代码
// 让它允许继承,可作用到子类
@Inherited
public @interface Greeting {
    // 使用枚举类型
    public enum FontColor {
        BLUE,RED,GREEN
    };
    String name();
    FontColor fontColor() default FontColor.RED;
}

读取注解信息的方法

编辑
属于重点,在系统中用到注解权限时非常有用,可以精确控制权限的粒度
注意:要想使用反射去读取注解,必须将 Retention 的值选为 Runtime
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Java代码
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
 
//读取注解信息
public class ReadAnnotationInfoTest {
    public static void main(String[] args) throws Exception {
        // 测试AnnotationTest类,得到此类的类对象
        Class c = Class.forName("com.iwtxokhtd.annotation.AnnotationTest");
        // 获取该类所有声明的方法
        Method[] methods = c.getDeclaredMethods();
        // 声明注解集合
        Annotation[] annotations;
        // 遍历所有的方法得到各方法上面的注解信息
        for (Method method : methods) {
            // 获取每个方法上面所声明的所有注解信息
            annotations = method.getDeclaredAnnotations();
            // 再遍历所有的注解,打印其基本信息
            System.out.println(method.getName());
            for (Annotation an : annotations) {
                System.out.println("方法名为:" + method.getName() + "其上面的注解为:" + an.annotationType().getSimpleName());
                Method[] meths = an.annotationType().getDeclaredMethods();
                // 遍历每个注解的所有变量
                for (Method meth : meths) {
                    System.out.println("注解的变量名为:" + meth.getName());
                }
            }
        }
    }
 
}

 

 

注解的作用:

             1、生成文档。这是最常见的,也是Java 最早提供的注解。常用的有 @see @param @return 等

             2、跟踪代码依赖性,实现替代配置文件功能。比较常见的是spring 2.5 开始的基于注解配置。作用就是减少配置。现在的框架基本都使用了这种配置来减少配置文件的数量。以后 java 的程序开发,最多的也将实现注解配置,具有很大用处;

             3、在编译时进行格式检查。如 @override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。

 

使用方法详解:

          下面是注解类, 其实注解也就是一个类文件

package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import entity.PersonChiness;
/***
 * Retention: 保持、保留
 * RetentionPolicy:政策、方针
 * @author huawei
 *@Retention
 *1、指示注释类型的注释要保留多久。如果注释类型声明中不存在 Retention 注释,则保留策略默认为 RetentionPolicy.CLASS
 *2、有三种取值 (代表三个阶段):
 * RetentionPolicy.SOURCE: 保留注解到 java 源文件阶段,例如 Override、SuppressWarnings
 * RetentionPolicy.CLASS: 保留注解到 class 文件阶段, 例如
 * RetentionPolicy.RUNTIME: 保留注解到运行时阶段即内存中的字节码, 例如 Deprecated
 */
// 元注解:表示的是注解的注解,(同义词有元信息、元数据)
// 如果不加,javac 会把这无用的注解丢掉
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})// 指定该注解使用的用处:用在 class 上和用在方法体上。
public @interface HelloAnnotation {
 // 返回值是 String 类型
 String color() default "蓝色";// 方法,但是相当于注解的属性,即:当成属性赋值,当成方法调用。
 // 默认 value
 String value();
 
 String author() default "默认给定了属性";
 // 返回值是数组对象
 int[] arrayAttr() default {1};
 // 返回值是注解类型
 MetaAnnotation annotationAttr()default @MetaAnnotation("");
 // 返回 CLASS 对象
 Class<PersonChiness> classType() default PersonChiness.class;
}

 

 

下面是实现注解的测试类:

package annotation;

/***
 * 注解类:
 * 1、相当于一种标记, 加上注解就等于为程序打上了某种标记, 没加, 则等于没加某种标记,
 * 2、以后 javac 编译器、开发工具或其他应用程序可以通过反射来了解你的类, 以及各种元素上有无何种标记,
 *   看你有什么标记,就去执行相应的命令和干相应的事。
 * 3、标记用处地方:
 *   加在包、类、字段、方法、方法的参数、局部变量
 * @author huawei
 *
 */
// 自定义注解
@HelloAnnotation(color="红色",
     value="如果只有 value 属性!可以不写属性名和等于号,直接写值即可!",
     arrayAttr={1,2,3},
     annotationAttr=@MetaAnnotation("返回注解类型规范形式。"))
public class AnnotationTest {
 @SuppressWarnings("deprecation")// 阻止警告
 @HelloAnnotation("当为 value 属性时,可以省掉属性名和等于号。")
 public static void main(String[] args) throws Exception{
  System.runFinalizersOnExit(true);
  
  if(AnnotationTest.class.isAnnotationPresent(HelloAnnotation.class)){
   HelloAnnotation helloAnnotation =
     (HelloAnnotation)AnnotationTest.class.getAnnotation(HelloAnnotation.class);
   System.out.println("color():"+helloAnnotation.color());
   System.out.println("value():"+helloAnnotation.value());
   System.out.println("author():"+helloAnnotation.author());
   System.out.println("arrayAttr():"+helloAnnotation.arrayAttr().length);
   System.out.println("annotationAttr():"+helloAnnotation.annotationAttr().value());
   System.out.println("classType():"+helloAnnotation.classType().newInstance().sayHello("hello,ketty"));
  }
 }
 @Deprecated// 自定义:备注过时的方法信息
 public static void sayHello(){
  System.out.println("hello,world");
 }
}

注解的作用:

             1、生成文档。这是最常见的,也是Java 最早提供的注解。常用的有 @see @param @return 等

             2、跟踪代码依赖性,实现替代配置文件功能。比较常见的是spring 2.5 开始的基于注解配置。作用就是减少配置。现在的框架基本都使用了这种配置来减少配置文件的数量。以后 java 的程序开发,最多的也将实现注解配置,具有很大用处;

             3、在编译时进行格式检查。如 @override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。

 

使用方法详解:

          下面是注解类, 其实注解也就是一个类文件

package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import entity.PersonChiness;
/***
 * Retention: 保持、保留
 * RetentionPolicy:政策、方针
 * @author huawei
 *@Retention
 *1、指示注释类型的注释要保留多久。如果注释类型声明中不存在 Retention 注释,则保留策略默认为 RetentionPolicy.CLASS
 *2、有三种取值 (代表三个阶段):
 * RetentionPolicy.SOURCE: 保留注解到 java 源文件阶段,例如 Override、SuppressWarnings
 * RetentionPolicy.CLASS: 保留注解到 class 文件阶段, 例如
 * RetentionPolicy.RUNTIME: 保留注解到运行时阶段即内存中的字节码, 例如 Deprecated
 */
// 元注解:表示的是注解的注解,(同义词有元信息、元数据)
// 如果不加,javac 会把这无用的注解丢掉
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})// 指定该注解使用的用处:用在 class 上和用在方法体上。
public @interface HelloAnnotation {
 // 返回值是 String 类型
 String color() default "蓝色";// 方法,但是相当于注解的属性,即:当成属性赋值,当成方法调用。
 // 默认 value
 String value();
 
 String author() default "默认给定了属性";
 // 返回值是数组对象
 int[] arrayAttr() default {1};
 // 返回值是注解类型
 MetaAnnotation annotationAttr()default @MetaAnnotation("");
 // 返回 CLASS 对象
 Class<PersonChiness> classType() default PersonChiness.class;
}

 

 

下面是实现注解的测试类:

package annotation;

/***
 * 注解类:
 * 1、相当于一种标记, 加上注解就等于为程序打上了某种标记, 没加, 则等于没加某种标记,
 * 2、以后 javac 编译器、开发工具或其他应用程序可以通过反射来了解你的类, 以及各种元素上有无何种标记,
 *   看你有什么标记,就去执行相应的命令和干相应的事。
 * 3、标记用处地方:
 *   加在包、类、字段、方法、方法的参数、局部变量
 * @author huawei
 *
 */
// 自定义注解
@HelloAnnotation(color="红色",
     value="如果只有 value 属性!可以不写属性名和等于号,直接写值即可!",
     arrayAttr={1,2,3},
     annotationAttr=@MetaAnnotation("返回注解类型规范形式。"))
public class AnnotationTest {
 @SuppressWarnings("deprecation")// 阻止警告
 @HelloAnnotation("当为 value 属性时,可以省掉属性名和等于号。")
 public static void main(String[] args) throws Exception{
  System.runFinalizersOnExit(true);
  
  if(AnnotationTest.class.isAnnotationPresent(HelloAnnotation.class)){
   HelloAnnotation helloAnnotation =
     (HelloAnnotation)AnnotationTest.class.getAnnotation(HelloAnnotation.class);
   System.out.println("color():"+helloAnnotation.color());
   System.out.println("value():"+helloAnnotation.value());
   System.out.println("author():"+helloAnnotation.author());
   System.out.println("arrayAttr():"+helloAnnotation.arrayAttr().length);
   System.out.println("annotationAttr():"+helloAnnotation.annotationAttr().value());
   System.out.println("classType():"+helloAnnotation.classType().newInstance().sayHello("hello,ketty"));
  }
 }
 @Deprecated// 自定义:备注过时的方法信息
 public static void sayHello(){
  System.out.println("hello,world");
 }
}

 

作者:郭无心
链接:https://www.zhihu.com/question/36486629/answer/70598262
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
优雅的使用 Java 注解的前提是理解 Java 注解, 并学习优秀的 Java 注解的使用 demo。

注解作用:每当你创建描述符性质的类或者接口时, 一旦其中包含重复性的工作,就可以考虑使用注解来简化与自动化该过程。

Java 提供了四种元注解,专门负责新注解的创建工作。


比如 Junit3 和 Junit4 ,比如 Servlet2 与 Servlet3 比如 Hibernate3 与 Hibernate4 比如 Spring2 之后的 Spring 版本,都引用注解这一机制,作用就是利用注解将一些本来重复性的工作,变成程序自动完成,简化和自动化该过程(PostScript:上述各个组件我也不是很熟悉,具体加入注解的版本是几不一定正确)。


元注解的作用就是负责注解其他注解。Java5.0 定义了 4 个标准的 meta-annotation 类型,它们被用来提供对其它 annotation 类型作说明。Java5.0 定义的元注解:

    1.@Target,
    2.@Retention,
    3.@Documented,
    4.@Inherited


  这些类型和它们所支持的类在 java.lang.annotation 包中可以找到。下面我们看一下每个元注解的作用和相应分参数的使用说明。


@Target

@Target 说明了 Annotation 所修饰的对象范围:Annotation 可被用于 packages、types(类、接口、枚举、Annotation 类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch 参数)。在 Annotation 类型的声明中使用了 target 可更加明晰其修饰的目标。

作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)

 取值 (ElementType) 有:

 1.CONSTRUCTOR:用于描述构造器
 2.FIELD:用于描述域
 3.LOCAL_VARIABLE:用于描述局部变量
 4.METHOD:用于描述方法
 5.PACKAGE:用于描述包
 6.PARAMETER:用于描述参数
 7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
例子:
Entity.java
/***
 *
 * 实体注解接口
 */
@Target(value = {ElementType.TYPE}) //仅应用于类、接口、enum声明、注解类型
@Retention(value = RetentionPolicy.RUNTIME) //运行时有效
public @interface Entity {
    /***
     * 实体默认firstLevelCache属性为false
     * @return boolean
     */
    boolean firstLevelCache() default false;
    /***
     * 实体默认secondLevelCache属性为false
     * @return boolean
     */
    boolean secondLevelCache() default true;
    /***
     * 表名默认为空
     * @return String
     */
    String tableName() default "";
    /***
     * 默认以""分割注解
     */
    String split() default "";
}

@Retention

 @Retention定义了该 Annotation 被保留的时间长短:某些 Annotation 仅出现在源代码中,而被编译器丢弃;而另一些却被编译在 class 文件中;编译在 class 文件中的 Annotation 可能会被虚拟机忽略,而另一些在 class 被装载时将被读取(请注意并不影响 class 的执行,因为 Annotation 与 class 在使用上是被分离的)。使用这个 meta-Annotation 可以对 Annotation 的“生命周期”限制。

作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

 取值(RetentionPoicy)有:

    1.SOURCE: 在源文件中有效(即源文件保留)
    2.CLASS: 在 class 文件中有效(即 class 保留)
    3.RUNTIME: 在运行时有效(即运行时保留)

/***
 * 字段注解接口
 */
@Target(value = {ElementType.FIELD})//注解可以被添加在属性上
@Retention(value = RetentionPolicy.RUNTIME)//注解保存在JVM运行时刻,能够在运行时刻通过反射API来获取到注解的信息
public @interface Column {
    String name();//注解的name属性
}

------------------------------------- 分割线 ---------------------------------------------------------
一个完整的例子
注解:DBTable.java
package annotations.database;
import java.lang.annotation.*;

@Target(ElementType.TYPE) // 应用于类、接口、enum、注解类型
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
public String name() default "";
}


注解:Constraints.java
package annotations.database;
import java.lang.annotation.*;

@Target(ElementType.FIELD) //用于变量名
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
boolean primaryKey() default false;
boolean allowNull() default true;
boolean unique() default false;
}

注解:SQLString.java
package annotations.database;
import java.lang.annotation.*;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
int value() default 0;
String name() default "";
Constraints constraints() default @Constraints;
}


注解:SQLInteger.java
package annotations.database;
import java.lang.annotation.*;

@Target(ElementType.FIELD)//FIELD 用于变量名
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
String name() default "";
Constraints constraints() default @Constraints;
}


类 Member.java
package annotations.database;

@DBTable(name = "MEMBER")
public class Member {
@SQLString(30) String firstName;
@SQLString(50) String lastName;
@SQLInteger Integer age;
@SQLString(value = 30,constraints = @Constraints(primaryKey = true))
String handle;

static int memberCount;
public String getHandle() { return handle; }
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public String toString() { return handle; }
public Integer getAge() { return age; }
}

------------------------------ 分割线 -----------------------------------
注解处理器 TableCreator.java
下面是一个注解处理器的例子,它将读取一个类文件,并检查其上的数据库注解,并生成用来创建数据库的 SQL 命令
package com.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import javax.swing.SpringLayout.Constraints;

public class TableCreator
{
public static void main(String[] args) throws Exception
{
if (args.length < 1)
{
System.out.println("arguments: annotated classes");
System.exit(0);
}
for (String className : args)
{
Class<?> cl = Class.forName(className);
DBTable dbTable = cl.getAnnotation(DBTable.class);
if (dbTable == null)
{
System.out.println("No DBTable annotations in class" + className);
continue;
}
String tableName = dbTable.name();
// If the name is empty, use the Class name:
if (tableName.length() < 1)
tableName = cl.getName().toUpperCase();
List<String> columnDefs = new ArrayList<String>();
for (Field field : cl.getDeclaredFields())
{
String columnName = null;
Annotation[] anns = field.getDeclaredAnnotations();
if (anns.length < 1)
continue; // Not a db table column
if (anns[0] instanceof SQLInteger)
{
SQLInteger sInt = (SQLInteger) anns[0];
// Use field name if name not specified
if (sInt.name().length()< 1)
columnName = field.getName().toUpperCase();
else
columnName = sInt.name();
columnDefs.add(columnName + "INT" + getConstraints(sInt.constraints()));
}
if (anns[0] instanceof SQLString)
{
SQLString sString = (SQLString) anns[0];
// Use field name if name not specified.
if (sString.name().length()< 1)
columnName = field.getName().toUpperCase();
else
columnName = sString.name();
columnDefs.add(columnName + "VARCHAR(" + sString.value() + ")" + getConstraints(sString.constraints()));
}
StringBuilder createCommand = new StringBuilder("CREATE TABLE" + tableName + "(");
for (String columnDef : columnDefs)
createCommand.append("\n" + columnDef + ",");
// Remove trailing comma
String tableCreate = createCommand.substring(0, createCommand.length() - 1)+ ");";
System.out.println("Table Creation SQL for" + className + "is :\n" + tableCreate);
}
}
}

private static String getConstraints(Constraints con)
{
    String constraints = "";
    if (!con.allowNull())
        constraints += " NOT NULL";
    if (con.primaryKey())
        constraints += " PRIMARY KEY";
    if (con.unique())
        constraints += " UNIQUE";
    return constraints;
}

} /*

  • Output: Table Creation SQL for annotations.database.Member is : CREATE
  • TABLE MEMBER(FIRSTNAME VARCHAR(30)); Table Creation SQL for
  • annotations.database.Member is : CREATE TABLE MEMBER( FIRSTNAME
  • VARCHAR(30), LASTNAME VARCHAR(50)); Table Creation SQL for
  • annotations.database.Member is : CREATE TABLE MEMBER( FIRSTNAME
  • VARCHAR(30), LASTNAME VARCHAR(50), AGE INT); Table Creation SQL for
  • annotations.database.Member is : CREATE TABLE MEMBER( FIRSTNAME
  • VARCHAR(30), LASTNAME VARCHAR(50), AGE INT, HANDLE VARCHAR(30) PRIMARY
  • KEY);
    */// :

Java 注解提供了关于代码的一些信息,但并不直接作用于它所注解的代码内容。在这个教程当中,我们将学习 Java 的注解,如何定制注解,注解的使用以及如何通过反射解析注解。

 

Java1.5 引入了注解,当前许多 java 框架中大量使用注解,如 Hibernate、Jersey、Spring。注解作为程序的元数据嵌入到程序当中。注解可以被一些解析工具或者是编译工具进行解析。我们也可以声明注解在编译过程或执行时产生作用。

在使用注解之前,程序源数据只是通过 java 注释和 javadoc,但是注解提供的功能要远远超过这些。注解不仅包含了元数据,它还可以作用于程序运行过程中、注解解释器可以通过注解决定程序的执行顺序。例如,在 Jersey webservice 我们为方法添加 URI 字符串的形式的 **PATH** 注解,那么在程序运行过程中 jerser 解释程序将决定该方法去调用所给的 URI。

创建 Java 自定义注解

创建自定义注解和创建一个接口相似,但是注解的 interface 关键字需要以 @符号开头。我们可以为注解声明方法。我们先来看看注解的例子,然后我们将讨论他的一些特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.journaldev.annotations;
 
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
    public @interface MethodInfo{
    String author() default 'Pankaj';
    String date();
    int revision() default 1;
    String comments();
}
  • 注解方法不能带有参数;
  • 注解方法返回值类型限定为:基本类型、String、Enums、Annotation 或者是这些类型的数组;
  •  注解方法可以有默认值;
  •  注解本身能够包含元注解,元注解被用来注解其它注解。

这里有四种类型的元注解:

1. @Documented —— 指明拥有这个注解的元素可以被 javadoc 此类的工具文档化。这种类型应该用于注解那些影响客户使用带注释的元素声明的类型。如果一种声明使用Documented进行注解,这种类型的注解被作为被标注的程序成员的公共 API。

2. @Target——指明该类型的注解可以注解的程序元素的范围。该元注解的取值可以为 TYPE,METHOD,CONSTRUCTOR,FIELD 等。如果Target元注解没有出现,那么定义的注解可以应用于程序的任何元素。

3. @Inherited——指明该注解类型被自动继承。如果用户在当前类中查询这个元注解类型并且当前类的声明中不包含这个元注解类型,那么也将自动查询当前类的父类是否存在Inherited元注解,这个动作将被重复执行知道这个标注类型被找到,或者是查询到顶层的父类。

4.@Retention——指明了该 Annotation 被保留的时间长短。RetentionPolicy 取值为 SOURCE,CLASS,RUNTIME。

 

Java 内建注解

Java 提供了三种内建注解。

1. @Override——当我们想要复写父类中的方法时,我们需要使用该注解去告知编译器我们想要复写这个方法。这样一来当父类中的方法移除或者发生更改时编译器将提示错误信息。

2. @Deprecated——当我们希望编译器知道某一方法不建议使用时,我们应该使用这个注解。Java 在 javadoc 中推荐使用该注解,我们应该提供为什么该方法不推荐使用以及替代的方法。

3. @SuppressWarnings——这个仅仅是告诉编译器忽略特定的警告信息,例如在泛型中使用原生数据类型。它的保留策略是 SOURCE(译者注:在源文件中有效)并且被编译器丢弃。

我们来看一个 java 内建注解的例子参照上边提到的自定义注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.journaldev.annotations;
 
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
 
public class AnnotationExample {
 
public static void main(String[] args) {
}
 
@Override
@MethodInfo(author = 'Pankaj', comments = 'Main method', date = 'Nov 17 2012', revision = 1)
public String toString() {
    return 'Overriden toString method';
}
 
@Deprecated
@MethodInfo(comments = 'deprecated method', date = 'Nov 17 2012')
public static void oldMethod() {
    System.out.println('old method, don't use it.');
}
 
@SuppressWarnings({ 'unchecked', 'deprecation' })
@MethodInfo(author = 'Pankaj', comments = 'Main method', date = 'Nov 17 2012', revision = 10)
public static void genericsTest() throws FileNotFoundException {
    List l = new ArrayList();
    l.add('abc');
    oldMethod();
}
 
}

相信这个例子可以不言自明并能展示在不同场景下的应用。

 

Java 注解解析
我们将使用反射技术来解析 java 类的注解。那么注解的 RetentionPolicy 应该设置为 RUNTIME 否则 java 类的注解信息在执行过程中将不可用那么我们也不能从中得到任何和注解有关的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.journaldev.annotations;
 
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
 
public class AnnotationParsing {
 
public static void main(String[] args) {
    try {
    for (Method method : AnnotationParsing.class
        .getClassLoader()
        .loadClass(('com.journaldev.annotations.AnnotationExample'))
        .getMethods()) {
        // checks if MethodInfo annotation is present for the method
        if (method.isAnnotationPresent(com.journaldev.annotations.MethodInfo.class)) {
            try {
        // iterates all the annotations available in the method
                for (Annotation anno : method.getDeclaredAnnotations()) {
                    System.out.println('Annotation in Method ''+ method + '' : ' + anno);
                    }
                MethodInfo methodAnno = method.getAnnotation(MethodInfo.class);
                if (methodAnno.revision() == 1) {
                    System.out.println('Method with revision no 1 = '+ method);
                    }
 
            } catch (Throwable ex) {
                    ex.printStackTrace();
                    }
        }
    }
    } catch (SecurityException | ClassNotFoundException e) {
            e.printStackTrace();
         }
    }
 
}

运行上面程序将输出:

1
2
3
4
5
6
Annotation in Method 'public java.lang.String com.journaldev.annotations.AnnotationExample.toString()' : @com.journaldev.annotations.MethodInfo(author=Pankaj, revision=1, comments=Main method, date=Nov 17 2012)
Method with revision no 1 = public java.lang.String com.journaldev.annotations.AnnotationExample.toString()
Annotation in Method 'public static void com.journaldev.annotations.AnnotationExample.oldMethod()' : @java.lang.Deprecated()
Annotation in Method 'public static void com.journaldev.annotations.AnnotationExample.oldMethod()' : @com.journaldev.annotations.MethodInfo(author=Pankaj, revision=1, comments=deprecated method, date=Nov 17 2012)
Method with revision no 1 = public static void com.journaldev.annotations.AnnotationExample.oldMethod()
Annotation in Method 'public static void com.journaldev.annotations.AnnotationExample.genericsTest() throws java.io.FileNotFoundException' : @com.journaldev.annotations.MethodInfo(author=Pankaj, revision=10, comments=Main method, date=Nov 17 2012)

这就是该教程的全部内容,希望你可以从中学到些东西。