什么是注解
Java 注解(Annotation)又称 Java 标注或元数据,是 JDK5.0 引入的新特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。例如我们常见的@Override、@Deprecated、@Test等。
简单来说,可以把注解理解成代码中的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过注解,开发人员可以在不改变原有代码情况下,在源代码中嵌入补充信息。
注解的作用
- 生成文档:通过代码里标识的元数据生成javadoc文档;
- 编译检查:通过代码里标识的元数据,让编译器在编译期间进行检查;
- 编译时动态处理:例如编译时通过代码里标识的元数据,动态生成代码;
- 运行时动态处理:运行时通过代码里标识的元数据动态处理,例如使用反射注入实例
注解语法和使用
通常,注解可分为以下三类:
1. 元注解
元注解是用于定义注解的注解,java.lang.annotation提供了四种元注解,专门注解其他的注解:
- @Documented: 标明是否生成javadoc文档
- @Retention: 标明注解被保留的阶段,即什么时候使用该注解
public enum RetentionPolicy {
SOURCE, /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了 */
CLASS, /* 编译器将Annotation存储于类对应的.class文件中,默认行为 */
RUNTIME /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}
例如
@Override
, 当它修饰一个方法的时候,表明该方法是重写父类的方法,编译期间会进行语法检查,但编译器处理完后,@Override
就没有任何作用。
- @Target: 标明注解使用的范围,即该注解用于什么地方
public enum ElementType {
TYPE, /* 描述类、接口(包括注释类型)或枚举 */
FIELD, /* 描述成员变量、对象、属性 */
METHOD, /* 用来描述方法 */
PARAMETER, /* 参数声明 */
CONSTRUCTOR, /* 构造方法声明 */
LOCAL_VARIABLE, /* 描述局部变量 */
ANNOTATION_TYPE, /* 注释类型声明 */
PACKAGE /* 包声明 */
}
- @Inherited: 标明注解可继承,是否允许子类继承该注解
2. Java常用注解
@Override: 标明重写某个方法
@Deprecated: 标明某个类或方法过时
@SuppressWarnings: 标明要忽略的警告,
当代码中使用这些注解后,编译器就会进行检查。
例如,这两段代码是@Override和@SuppressWarnings的Java源文件:
[Override.java]
@Target(ElementType.METHOD) /* 表明@Override用于描述方法 */
@Retention(RetentionPolicy.SOURCE) /* 表明@Override仅在于编译器处理期间存在 */
public @interface Override {
}
[SuppressWarnings.java]
/* 表明@SuppressWarnings可以描述方法、字段、参数、构造方法类等 */
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE) /* 表明@SuppressWarnings仅在于编译器处理期间存在 */
public @interface SuppressWarnings {
String[] value();
}
@interface: 表示实现了 java.lang.annotation.Annotation 接口,即该注解就是一个Annotation。
定义 Annotation 时,@interface 是必须的。
注意:它和通常的 implemented 实现接口的方法不同,Annotation 接口的实现细节都由编译器完成。
通过 @interface 定义注解后,该注解不能继承其他的注解或接口。
@Override使用很常见,来看一个使用@SuppressWarnings的例子:
@SuppressWarnings("deprecation")
private void push() {
......
}
如果push() 方法是过期的方法,编译时就会产生警告。而使用了 @SuppressWarnings(value={"deprecation"})后,编译器会对"调用 push() 产生的警告"保持沉默。
3. 自定义注解
可以根据自己的需求定义注解,如写UT/IT case 时使用到的@Test,Google 开源依赖注入框架Dagger2中的@Inject、@Module等
@Test注解后,在运行该方法时,测试框架会自动识别该方法并单独调
注解处理器
如果没有注解处理器,那注解跟注释其实没有什么区别,下面是Thinking Java的一个Demo,使用注解创建数据表:
1. 定义用来创建表名的注解@DBTable:
//DBTable.java
@Target(ElementType.TYPE) //Applies to classes only
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
public String name() default "";
}
2. 定义用来约束的注解@Constraints:
//Constraints.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
boolean primarykey() default false;
boolean allowNull() default true;
boolean unique() default false;
}
3. 定义声明String类型的注解@SQLString:
//SQLString.java
@Target(ElementType.FIELD)
public @interface SQLString {
int value() default 0;
String name() default "";
Constraints constraints() default @Constraints;
}
4. 定义声明Integer类型的注解@SQLInteger:
//SQLInteger.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
String name() default "";
Constraints constraints() default @Constraints;
}
注意在SQLString.java和SQLInteger.java中constraints()元素的默认值就是@Constraints注解设定的默认值。如果现在要令嵌入的@Constraints注解中的unique()元素为true, 并以此作为constraints元素的默认值,则需要如下定义该元素
//Uniqueness.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Uniqueness {
Constraints constraints() default @Constraints(unique = true)
}
5. 使用以上这些注解,定义一个数据库表:
//Member.java
@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 getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public Integer getAge() {
return age;
}
public String getHandle() {
return handle;
}
public static int getMemberCount() {
return memberCount;
}
}
6. 实现注解处理器:
//TableCreator.java
{Args:com.sdkd.database.Member}
public class TableCreator {
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;
}
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;
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];
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 + ",");
String tableCreate = createCommand.substring(0, createCommand.length() - 1) + ");";
System.out.println("Table Creation SQL for " +
className + " is :\n" + tableCreate);
}
}
}
}
7. main()方法测试:
public class Test {
public static void main(String[] args) throws Exception {
String[] arg = {"com.sdkd.database.Member"};
new TableCreator().main(arg);
}
}
7. 输出:
Table Creation SQL for com.sdkd.database.Member is :
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30));
Table Creation SQL for com.sdkd.database.Member is :
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30),
LASTNAME VARCHAR(50));
Table Creation SQL for com.sdkd.database.Member is :
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30),
LASTNAME VARCHAR(50),
AGE INT);
Table Creation SQL for com.sdkd.database.Member is :
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30),
LASTNAME VARCHAR(50),
AGE INT,
HANDLE VARCHAR(30)PRIMARY KEY);