Java 自定义注解开发
什么是注解
注解就是 Java 提供了一种为程序元素关联任何信息或任何元数据 (metadata)的途径和方法。注解是一个接口,程序可以通过反射来获取指定程序元素的 Annotion 对象,然后通过 Annotation 对象来获取注解里面的元数据
注解位置
Annotation 是在 JDK 1.5 之后被引入的,它可以用来创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。从某些方面看,annotation 就像修饰符一样被使用,并应用于包、类型、构造方法、方法、成员变量、参数、本地变量的声明中。这些信息被存储在 Annotation 的 “name = value” 结构中。
注解成员提供的关联信息(参数、注解属性)
Annotation的成员在Annotation类型中以无参数的方法的形式被声明。其方法名和返回值定义了该成员的名字和类型。在此有一个特定的默认 语法:允许声明任何Annotation成员的默认值。一个Annotation可以将name=value对作为没有定义默认值的Annotation 成员的值,当然也可以使用name=value对来覆盖其它成员默认值。这一点有些近似类的继承特性,父类的构造函数可以作为子类的默认构造函数,但是也 可以被子类覆盖。
注解不会影响程序代码的执行
Annotation能被用来为某个程序元素(类、方法、成员变量等)关联任何的信息。需要注意的是,这里存在着一个基本的规则:Annotation不能影响程序代码的执行,无论增加、删除 Annotation,代码都始终如一的执行。另外,尽管一些annotation通过java的反射api方法在运行时被访问,而java语言解释器在工作时忽略了这些annotation。正是由于java虚拟机忽略了Annotation,导致了annotation类型在代码中是“不起作用”的; 只有通过某种配套的工具才会对annotation类型中的信息进行访问和处理
注解的作用
注解(Annotation) 为我们在代码中添加信息提供了一种形式化的方法,是我们可以在稍后 某个时刻方便地使用这些数据(通过 解析注解 来使用这些数据),常见的作用有以下几种:
- 生成文档。这是最常见的,也是java 最早提供的注解。常用的有@see @param @return 等;
- 在编译时进行格式检查。如@Override放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出;
- 跟踪代码依赖性,实现替代配置文件功能。比较常见的是spring 2.5 开始的基于注解配置。作用就是减少配置。现在的框架基本都使用了这种配置来减少配置文件的数量;
JDK 自带的注解
@Override
@Override 表示当前方法覆盖了父类的方法
此注释只适用于修辞方法,表示一个方法声明打算重写超类中的另一个方法声明。如果方法利用此注释类型进行注解但没有重写超类方法,则编译器会生成一条错误消息
@Deprecated
@Deprecated 表示方法已经过时,方法上有横线,使用时会有警告。
此注释可用于修辞方法、属性、类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。在使用不被赞成的程序元素或在不被赞成的代码中执行重写时,编译器会发出警告
@SuppressWarings
@SuppressWarings 表示关闭一些警告信息(通知java编译器忽略特定的编译警告)
用来抑制编译时的警告信息。与前两个注释有所不同,你需要添加一个参数才能正确使用,这些参数值都是已经定义好了的,我们选择性的使用就好了。如果需要添加多种类型的警告,可以使用 @SuppressWarings({“rawtypes”,“unused”})
public class OverrideDemoImpl implements OverrideDemo {
// Override 注解会告诉编译器,此方法会执行父类方法覆盖,编译器会按照方法覆盖的要求进行检查
@Override
public String overrideDemo() {
return null;
}
// @Deprecated 表示方法已经过时,方法上会出现横线提示
@Deprecated
public String deprecatedDemo(){
return null;
}
// @deprecated 标记的方法,使用时会有警告提醒,但不影响正常使用
public void init(){
new OverrideDemoImpl().deprecatedDemo();
}
// 抑制单类型的警告和抑制所有类型的警告使用方式
@SuppressWarnings("all")
public void addItems(String item){
List items = new ArrayList();
items.add(item);
}
// 抑制多类型的警告
@SuppressWarnings(value={"unchecked", "rawtypes"})
public void addItem(String item){
List items = new ArrayList();
items.add(item);
}
}
抑制警告的关键字
关键字 | 用途 |
---|---|
all | 全部类型的警告 |
boxing | to suppress warnings relative to boxing/unboxing operations |
cast | to suppress warnings relative to cast operations |
dep-ann | to suppress warnings relative to deprecated annotation |
deprecation | 使用了某些不赞成使用的类和方法 |
fallthrough | switch语句执行到底没有break关键字 |
finally | 任何finally子句不能正常完成时的警告 |
hiding | to suppress warnings relative to locals that hide variable |
incomplete-switch | to suppress warnings relative to missing entries in a switch statement (enum case) |
nls | to suppress warnings relative to non-nls string literals |
null | to suppress warnings relative to null analysis |
path | 在类路径,原文件路径中有不存在的路径 |
rawtypes | 没有传递带有泛型的参数 |
restriction | to suppress warnings relative to usage of discouraged or forbidden references |
resource | 有泛型未指定类型 |
serial | 某类实现Serializable 但是没有定义serialVersionUID 这个需要但是不必须的字段 |
static-access | o suppress warnings relative to incorrect static access |
synthetic-access | to suppress warnings relative to unoptimized access from inner classes |
unchecked | 未检查的转化,如集合没有指定类型 |
unqualified-field-access | to suppress warnings relative to field access unqualified |
unused | 未使用的变量 |
开发自定义注解
自定义注解的语法规则
- 使用@interface关键字定义注解,注意关键字的位置
- 使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。
- 成员以无参数无异常的方式声明,注意区别一般类成员变量的声明
- 其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称
- 可以使用default为成员指定一个默认值
- 成员类型是受限的,合法的类型包括原始类型以及String、Class、Annotation、Enumeration (JAVA的基本数据类型有8种:byte(字节)、short(短整型)、int(整数型)、long(长整型)、float(单精度浮点数类型)、double(双精度浮点数类型)、char(字符类型)、boolean(布尔类型)
- 注解类可以没有成员,没有成员的注解称为标识注解,例如JDK注解中的@Override、@Deprecation
- 如果注解只有一个成员,并且把成员取名为value(),则在使用时可以忽略成员名和赋值号“=” ,例如JDK注解的@SuppviseWarnings ;如果成员名 不为value,则使用时需指明成员名和赋值号"=",
元注解
就是注解的注解,就是给你自己定义的注解添加注解,你自己定义了一个注解,但你想要你的注解有什么样的功能,此时就需要用元注解对你的注解进行说明了。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。
@Target
@Target说明了Annotation所修饰的对象范围:即注解的作用域,用于说明注解的使用范围(即注解可以用在什么地方,比如类的注解,方法注解,成员变量注解等等)
注意:如果Target元注解没有出现,那么定义的注解可以应用于程序的任何元素。
取值范围是在java.lang.annotation.ElementType这个枚举中:
Target类型 | 描述 |
---|---|
ElementType.TYPE | 应用于类、接口(包括注解类型)、枚举 |
ElementType.FIELD | 应用于属性(包括枚举中的常量) |
ElementType.METHOD | 应用于方法 |
ElementType.PARAMETER | 应用于方法的形参 |
ElementType.CONSTRUCTOR | 应用于构造函数 |
ElementType.LOCAL_VARIABLE | 应用于局部变量 |
ElementType.ANNOTATION_TYPE | 应用于注解类型 |
ElementType.PACKAGE | 应用于包 |
ElementType.TYPE_PARAMETER | 1.8版本新增,应用于类型变量) |
ElementType.TYPE_USE | 1.8版本新增,应用于任何使用类型的语句中(例如声明语句、泛型和强制转换语句中的类型) |
@Retention
@Retention定义了该Annotation被保留的时间长短:
- 某些Annotation仅出现在源代码中,而被编译器丢弃;
- 而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,
- 而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的。
- 使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
@Retention的取值范围是在 RetentionPoicy 枚举中
生命周期类型 | 描述 |
---|---|
RetentionPolicy.SOURCE | 编译时被丢弃,不包含在类文件中 |
RetentionPolicy.CLASS | JVM加载时被丢弃,包含在类文件中,默认值 |
RetentionPolicy.RUNTIME | 由JVM 加载,包含在类文件中,在运行时可以被获取到 |
@Documented
@Documented 用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共API,因此可以被例如 javadoc 此类的工具文档化。Documented 是一个标记注解,没有成员。
@Inherited
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继 承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现, 或者到达类继承结构的顶层。
/**
* @Target 说明的是当前注解的作用域,即注解的使用范围 1.CONSTRUCTOR:用于描述构造器 2.FIELD:用于描述域 3.LOCAL_VARIABLE:用于描述局部变量
* 4.METHOD:用于描述方法 5.PACKAGE:用于描述包 6.PARAMETER:用于描述参数 7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
*/
@Target({ElementType.METHOD, ElementType.TYPE})
/**
* @Retention 定义了该注解存在的周期
* 1.SOURCE:在源文件中有效(即源文件保留)
* 2.CLASS:在class文件中有效(即class保留)
* 3.RUNTIME:在运行时有效(即运行时保留)
*/
@Retention(RetentionPolicy.RUNTIME)
/**
* @Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了 @Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该 class 的子类。
* 即 @Inherited 修饰的 Annotation 在标注 class 时,其子类相应位置也会自定附带上此 Annotation 注解
*/
@Inherited
/**
* @Documented 用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API ,因此可以被例如 javadoc 此类的工具文档化。
*/
@Documented
public @interface AnnotationDemo {}
注解开发实例
利用反射获取注解信息,结合当前方法参数进行组合操作
Column
@Target(ElementType.FIELD) // 该注解只作用在属性上
@Retention(RetentionPolicy.RUNTIME) // 运行时保留
public @interface Column {
// 字段名称
String columnName();
// 字段类型
String dataType();
// 字段长度
int dataLength() default 10;
String constraint() default "";
}
Table
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String tableName();
}
User
@Table(tableName = "tab_user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Column(columnName = "id", dataType = "int", dataLength = 6, constraint = "primary key")
private int id;
@Column(columnName = "name", dataType = "varchar", dataLength = 10, constraint = "unique")
private String name;
@Column(columnName = "password", dataType = "varchar", dataLength = 10)
private String password;
public User(int id, String name, String password){
this.id = id;
this.name = name;
this.password = password;
}
}
AutoCreateTable
public class AutoCreateTable {
public static void main(String[] args) {
buildSql();
}
public static void buildSql() {
StringBuffer str = new StringBuffer("create table ");
User user = new User(1, "zhangsan", "123456");
// 获取 User 类中 Table 注解
Table table = (Table) user.getClass().getAnnotation(Table.class);
str.append(table);
Field[] fields = user.getClass().getDeclaredFields();
for (Field field : fields) {
// 获取的属性值包含 serialVersionUID, serialVersionUID.getAnnotation() 为null
if(!"serialVersionUID".equals(field.getName())){
Column column = (Column) field.getAnnotation(Column.class);
String columnName = column.columnName();
str.append(", columnName : " + columnName);
String columnType = column.dataType();
str.append(", columnType : " + columnType);
int dataLength = column.dataLength();
str.append(", dataLength : " + dataLength);
String constraint = column.constraint();
str.append(", constraint : " + constraint);
}
}
System.out.println(str);
}
}