本文部分摘自 On Java 8

基本语法

注解是 Java 5 所引入的众多语言变化之一,是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用,包含在 java.lang.annotation 包中

注解的语法十分简单,只要在现有语法中添加 @ 符号即可,java.lang 包提供了如下五种注解:

@Override

表示当前的方法定义将覆盖基类的方法,如果你不小心把方法签名拼错了,编译器就会发出错误提示

@Deprecated

如果使用该注解的元素被调用,编译器就会发出警告信息,表示不鼓励程序员使用

@SuppressWarnings

关闭不当的编译器警告信息

@SafeVarargs

禁止对具有泛型可变参数的方法或构造函数的调用方发出警告

@FunctionalInterface

声明接口类型为函数式接口

定义注解

注解的定义看起来和接口的定义很像,事实上它们和其他 Java 接口一样,也会被编译成 class 文件

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface TestAnnotation {}

除开 @ 符号, @Test 的定义看起来更像一个空接口。注解的定义也需要一些元注解,元注解用于注解其他的注解

注解

解释

@Target

表示注解可以用于哪些地方。可能的 ElementType 参数包括:

CONSTRUCTOR:构造器的声明

FIELD:字段声明(包括 enum 实例)

LOCAL_VARIABLE:局部变量声明

METHOD:方法声明

PACKAGE:包声明

PARAMETER:参数声明

TYPE:类、接口(包括注解类型)或者 enum 声明

@Retention

表示注解信息保存的时长。可选的 RetentionPolicy 参数包括:

SOURCE:注解将被编译器丢弃

CLASS:注解在 class 文件中可用,但是会被 VM 丢弃

RUNTIME:VM 将在运行期也保留注解,因此可以通过反射机制读取注解的信息

@Documented

将此注解保存在 Javadoc 中

@Inherited

允许子类继承父类的注解

@Repeatable

允许一个注解可以被使用一次或者多次(Java8)

不包含任何元素的注解称为标记注解,上例中的 @Test 就是标记注解。注解通常也会包含一些表示特定值的元素,当分析处理注解的时候,程序可以利用这些值。注解的元素看起来就像接口的方法,但可以为其指定默认值

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface TestAnnotation {

int id();

String description() default "no description";

}

....

public class TestUtils {

// 在方法上使用注解 @TestAnnotation

@UseCase(id = 47, description = "description")

public void test() {

...

}

}

注解元素可用的类型如下所示,如果使用了其他类型,编译器就会报错:

所有基本类型(int、float、boolean 等)

String

Class

enum

Annotation

以上类型的数组

如果没有给出 description 的值,在分析处理这个类的时候会使用该元素的默认值。元素的默认值不能有不确定的值,也就是说,元素要么有默认值,要么就在使用注解时提供元素的值

这里还有另外一个限制:任何非基本类型的元素,无论是在源代码声明时还是在注解接口中定义默认值时,都不能使用 null 作为值。如果我们希望表现一个元素的存在或者缺失的状态,可以自定义一些特殊的值,比如空字符串或者负数用于表达某个元素不存在

注解不支持继承,你不能使用 extends 关键字来继承 @interface

注解处理器

如果没有用于读取注解的工具,那么注解不会比注释更有用。使用注解中一个很重要的作用就是创建与使用注解处理器。Java 拓展了反射机制的 API 用于帮助你创造这类工具。同时他还提供了 javac 编译器钩子在编译时使用注解

下面是一个非常简单的注解处理器,我们用它来读取被注解的 TestUtils 类,并且使用反射机制来寻找 @TestAnnotation 标记

public class TestAnnotationTracker {

public static void trackTestAnnotation(Class> cl) {

for(Method m : cl.getDeclaredMethods()) {

TestAnnotation ta = m.getAnnotation(TestAnnotation.class);

if(ta != null) {

System.out.println(ta.id() + "\n " + ta.description());

}

}

}

public static void main(String[] args) {

trackTestAnnotation(TestUtils.class);

}

}

这里用到了两个反射的方法:getDeclaredMethods() 和 getAnnotation(),getAnnotation() 方法返回指定类型的注解对象,在本例中就是 TestAnnotation,如果被注解的方法上没有该类型的注解,返回值就为 null。通过调用 id() 和 description() 方法来提取元素值

使用注解实现对象 - 数据库映射

当有些框架需要一些额外的信息才能与你的源代码协同工作,这种情况下注解就会变得十分有用。自定义例如对象/关系映射工具(Hibernate 和 MyBatis)通常都需要 XML 描述文件,而这些文件脱离于代码之外。除了定义 Java 类,程序员还必须重复的提供某些信息,而例如类名和包名等信息已经在原始类中提供过了,经常会导致代码和描述文件的同步问题

假设你现在想提供一些基本的对象/关系映射功能,能够自动生成数据库表。你可以使用 XML 描述文件来指明类的名字、每个成员以及数据库映射的相关信息。但是,通过使用注解,你可以把所有信息都保存在 JavaBean 源文件中。为此你需要一些用于定义数据库表名称、数据库列以及将 SQL 类型映射到属性的注解

首先创建一个用来映射数据库表的注解,用来修饰类、接口(包括注解类型)或者 enum 声明

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

public @interface DBTable {

String name() default "";

}

如下是修饰字段的注解

@Target(ElementType.FIELD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Constraints {

boolean primaryKey() default false;

boolean allowNull() default true;

boolean unique() default false;

}

public @interface SQLString {

int value() default 0;

String name() default "";

Constraints constraints() default @Constraints;

}

@Target(ElementType.FIELD)

@Retention(RetentionPolicy.RUNTIME)

public @interface SQLInteger {

String name() default "";

Constraints constraints() default @Constraints;

}

@Constraints 代表了数据库通常提供的约束的一小部分,primaryKey(),allowNull() 和 unique() 元素都提供了默认值,大多数情况下,注解的使用者都不需要输入太多东西

另外两个 @interface 定义的是 SQL 类型。如果希望这个框架更有价值的话,我们应该为每个 SQL 类型都定义相应的注解。不过作为示例,两个元素足够了。这些 SQL 类型具有 name() 元素和 constraints() 元素。后者利用了嵌套注解的功能,将数据库列的类型约束信息嵌入其中。注意 constraints() 元素的默认值是 @Constraints,没有在括号中指明 @Constraints 元素的值,因此,constraints() 的默认值为所有元素都为默认值。如果要使得嵌入的 @Constraints 注解中的 unique() 元素为 true,并作为 constraints() 元素的默认值,你可以像如下定义:

public @interface Uniqueness {

Constraints constraints() default @Constraints(unique = true);

}

下面是一个简单的,使用了如上注解的类

@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 reference;

static int memberCount;

public String getReference() { return reference; }

public String getFirstName() { return firstName; }

public String getLastName() { return lastName; }

@Override

public String toString() { return reference; }

public Integer getAge() { return age; }

}

类注解 @DBTable 注解给定了元素值 MEMBER,它将会作为表的名字。类的属性 firstName 和 lastName 都被注解为 @SQLString 类型并且给了默认元素值分别为 30 和 50,并在嵌入的 @Constraint 注解中设定 primaryKey 元素的值

下面是一个注解处理器的例子,它将读取一个类文件,检查上面的数据库注解,并生成用于创建数据库的 SQL 命令:

public class TableCreator {

public static void generateSql(String[] classnames) throws Exception {

for (String className : classnames) {

Class> cl = Class.forName(className);

DBTable dbTable = cl.getAnnotation(DBTable.class);

String tableName = dbTable.name();

// 如果表名为空字符串,则使用类名

if (tableName.length() < 1) {

tableName = cl.getName().toUpperCase();

}

List columnDefs = new ArrayList<>();

for (Field field : cl.getDeclaredFields()) {

String columnName = null;

Annotation[] anns = field.getDeclaredAnnotations();

// 该属性不是列

if (anns.length < 1) {

continue;

}

// 处理整数类型

if (anns[0] instanceof SQLInteger) {

SQLInteger sInt = (SQLInteger) anns[0];

// 如果列名为空字符串,则使用属性名

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];

if (sString.name().length() < 1) {

columnName = field.getName().toUpperCase();

} else {

columnName = sString.name();

}

columnDefs.add(columnName + " VARCHAR(" + sString.value() + ")" +

getConstraints(sString.constraints()));

}

// 构造并输出 sql 字符串

StringBuilder createCommand = new StringBuilder("CREATE TABLE " + tableName + "(");

for (String columnDef : columnDefs) {

createCommand.append("\n " + columnDef + ",");

}

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;

}

}

编译时注解处理

当 @Retention 的 RetentionPolicy 参数被标注为 SOURCE 或 CLASS,此时你无法通过反射去获取注解信息,因为注解在运行期是不存在的。使用 javac 可以创建编译时注解处理器,在编译时扫描和处理注解。你可以自定义注解,并注册到对应的注解处理器。注解处理器可以生成 Java 代码,这些生成的 Java 代码会组成新的 Java 源文件,但不能修改已经存在的 Java 类,例如向已有的类中添加方法。如果你的注解处理器创建了新的源文件,在新一轮处理中注解处理器会检查源文件本身,在检测一轮之后持续循环,直到不再有新的源文件产生,然后编译所有的源文件

我们来编写一个简单的注解处理器,如下是注解的定义

@Retention(RetentionPolicy.SOURCE)

@Target({ElementType.TYPE, ElementType.METHOD,

ElementType.CONSTRUCTOR,

ElementType.ANNOTATION_TYPE,

ElementType.PACKAGE, ElementType.FIELD,

ElementType.LOCAL_VARIABLE})

public @interface Simple {

String value() default "-default-";

}

@Retention 的参数为 SOURCE,这意味着注解不会存留在编译后的 class 文件,因为这对应编译时处理注解是没有必要的,在这里,javac 是唯一有机会处理注解的方式

package annotations.simplest;

@Simple

public class SimpleTest {

@Simple

int i;

@Simple

public SimpleTest() {}

@Simple

public void foo() {

System.out.println("SimpleTest.foo()");

}

@Simple

public void bar(String s, int i, float f) {

System.out.println("SimpleTest.bar()");

}

@Simple

public static void main(String[] args) {

@Simple

SimpleTest st = new SimpleTest();

st.foo();

}

}

运行 main 方法,程序就会开始编译,如下是一个简单的处理器,作用就是把注解相关的信息打印出来

package annotations.simplest;

import javax.annotation.processing.*;

import javax.lang.model.SourceVersion;

import javax.lang.model.element.*;

import java.util.*;

@SupportedAnnotationTypes("annotations.simplest.Simple")

@SupportedSourceVersion(SourceVersion.RELEASE_8)

public class SimpleProcessor extends AbstractProcessor {

@Override

public boolean process(Set extends TypeElement> annotations,

RoundEnvironment env) {

for(TypeElement t : annotations) {

System.out.println(t);

}

for(Element el : env.getElementsAnnotatedWith(Simple.class)) {

display(el);

}

return false;

}

private void display(Element el) {

System.out.println("==== " + el + " ====");

System.out.println(el.getKind() +// 返回此元素的种类,字段,方法,或是类

" : " + el.getModifiers() +// 返回此元素的修饰符

" : " + el.getSimpleName() +// 返回此元素的简单名称

" : " + el.asType());// 返回此元素定义的类型

// 如果元素为CLASS类型,动态向下转型为更具体的元素类型,并打印相关信息

if(el.getKind().equals(ElementKind.CLASS)) {

TypeElement te = (TypeElement)el;

System.out.println(te.getQualifiedName());

System.out.println(te.getSuperclass());

System.out.println(te.getEnclosedElements());

}

// 如果元素为METHOD类型,动态向下转型为更具体的元素类型,并打印相关信息

if(el.getKind().equals(ElementKind.METHOD)) {

ExecutableElement ex = (ExecutableElement)el;

System.out.print(ex.getReturnType() + " ");

System.out.print(ex.getSimpleName() + "(");

System.out.println(ex.getParameters() + ")");

}

}

}

使用 @SupportedAnnotationTypes 和 @SupportedSourceVersion 注解来确定支持哪些注解以及支持的 Java 版本

注解处理器需要继承抽象类 javax.annotation.processing.AbstractProcessor,唯一需要实现的方法就是 process(),这里是所有行为发生的地方。第一个参数获取到此注解处理器所要处理的注解集合,第二个参数保留了剩余信息,这里我们所做的事情只是打印了注解(只存在一个)。process() 中实现的第二个操作是循环所有被 @Simple 注解的元素,并且针对每一个元素调用 display() 方法。展示所有 Element 自身的基本信息

Java注解库_Java 注解详解相关推荐

  1. 泛型java 代码讲解_Java泛型详解

    2516326-5475e88a458a09e4.png 一,打破砂锅问到底 泛型存在的意义? 泛型类,泛型接口,泛型方法如何定义? 如何限定类型变量? 泛型中使用的约束和局限性有哪些? 泛型类型的继 ...

  2. java泛型 初始化_Java泛型详解

    概述 泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用. 什么是泛型?为什么要使用泛型? 泛型,即"参数化类型".一提到参数,最熟悉的就是定义方法时有 ...

  3. java递归函数例子_Java递归函数详解附案例

    递归函数在Java语言中得到了广泛的应用,它使得程序的编写过程更加的清晰明了.对Java初学者来讲,递归函数是需要学习的一个重要知识点.本文将附上案例和演示代码,具体为大家介绍递归函数的概念.要素以及 ...

  4. java 观察者模式讲解_java观察者模式详解

    简单地说,观察者模式定义了一个一对多的依赖关系,让一个或多个观察者对象监察一个主题对象.这样一个主题对象在状态上的变化能够通知所有的依赖于此对象的那些观察者对象,使这些观察者对象能够自动更新. 观察者 ...

  5. java 第三方库common系统详解

    IT技术员最讨厌的一件事应该是重复造轮子,我就直接引用大神的文章了,转载也为了以后好找: http://zhoualine.iteye.com/blog/1770014 如何稍微注意平常的库,会发现很 ...

  6. java正则表达式原理_Java 正则表达式详解

    摘自:http://www.jb51.net/article/16829.htm 许多语言,包括 Perl.PHP.Python.JavaScript和JScript,都支持用正则表达式处理文本,一些 ...

  7. java生日正则表达式_Java正则表达式详解

    如果你曾经用过Perl或任何其他内建正则表达式支持的语言,你一定知道用正则表达式处理文本和匹配模式是多么简单.如果你不熟悉这个术语,那么"正则表达式"(Regular Expres ...

  8. java iterator对象_JAVA Iterator 详解 代码

    Iterator接口 1.所有实现了Collection接口的容器类都有一个Iterator方法用以返回一个实现了Iterator接口的对象 2.Iterator对象称为迭代器,用以方便的操作实现对象 ...

  9. java 函数内部类_java 内部类详解 转

    classOuter {classInner { } } (二) 内部类的访问规则 ​ A:可以直接访问外部类的成员,包括私有 ​ B:外部类要想访问内部类成员,必须创建对象 (三) 内部类的分类 ​ ...

最新文章

  1. 《Lancet》发表全球学者联合声明!
  2. [转]SQL Server 2005 分区表实践——建立分区表(partition table)
  3. 运行SSIS包的几种方式
  4. 看完你会为自己哭,或者为他们哭
  5. 前端小白的 docker 配置nginx踩坑之旅
  6. Linux dig
  7. 百度代码规范 -- PHP
  8. 配置解压版本的Tomcat为Windows服务
  9. 连点器安卓手机版_鼠大侠手机版下载-鼠大侠鼠标连点器手机版下载 v1.4 安卓版...
  10. Python用正则表达式匹配ABAC和AABB的词语
  11. python go rpc_Python RPC 之 gRPC
  12. php shuffle有种子吗6,6个BT种子网站,没有它找不到的资源!太少人知道了怪可惜的...
  13. DSP生成bin文件方法
  14. 富文本编辑器NicEdit的使用
  15. 郑州大学计算机上机模拟题库,郑州大学VB考试模拟试题
  16. jenkins如何清缓存_Maven本地缓存清理小工具
  17. Android 传感器分类
  18. 软件测试面试题:你们公司的测试流程是怎么样的?
  19. 展望2020:游戏本地化
  20. 二级c语言 真题,全国计算机二级c语言历年真题完整版.doc

热门文章

  1. c语言求方程组的自然数解,多元一次线性方程自然数解的算法解决办法
  2. java添加删除用户信息泄露_java删除用户404错误!!神奇!增改查都没问题啊!!...
  3. ubuntu查看OpenCV的版本和安装的库
  4. xgboost 的 get_fscore()
  5. 111. Leetcode 300. 最长递增子序列 (动态规划-子序列问题)
  6. 90. Leetcode 剑指 Offer 62. 圆圈中最后剩下的数字 (动态规划-基础题)
  7. 概率统计笔记: 卡方分布(介绍)
  8. Flink从入门到精通100篇(十一)-Java SPI 机制在 Flink SQL 中的应用
  9. 让bug无处藏身,Java 线上问题排查思路、常用工具
  10. 机器学习中的矩阵向量求导(四) 矩阵向量求导链式法则