描述
java注解是在JDK5时引入的新特性,Java注解与普通修饰符(public、static、void等)的使用方式并没有多大区别,可以修饰java对象元素。
声明注解
//自定义声明的注解,可以提供java元素调用
@Target(ElementType.METHOD)//元注解
@Retention(RetentionPolicy.RUNTIME)//元注解
public @interface Test {
}
元注解
用来给其他注解打标签的注解,即用来注解其他注解的注解。元注解共有6个。
@Retention:用于指定被此元注解标注的注解的保留时长,源代码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
从源代码中可以看出,其有一个属性value,返回一个枚举RetentionPolicy 类型,有3种类型:
RetentionPolicy.SOURCE: :注解信息只保留在源代码中,编译器编译源码时会将其直接丢弃。
RetentionPolicy.CLASS::注解信息保留在class文件中,但是虚拟机VM不会持有其信息。编译时获取注解信息,并据此产生java代码文件,无性能损失
RetentionPolicy.RUNTIME::注解信息保留在class文件中,而且VM也会持有此注解信息,所以可以通过反射的方式获得注解信息。 运行时获取并使用注解信息,此方式属于反射范畴,有性能损失
@Target:用于指定被此元注解标注的注解可以标注的程序元素,源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
从源码中可以看出,其有一个属性value
,返回一个枚举ElementType
类型的数组
public enum ElementType {
/**标明该注解可以用于类、接口(包括注解类型)或enum声明*/
TYPE,
/** 标明该注解可以用于字段(域)声明,包括enum实例 */
FIELD,
/** 标明该注解可以用于方法声明 */
METHOD,
/** 标明该注解可以用于参数声明 */
PARAMETER,
/** 标明注解可以用于构造函数声明 */
CONSTRUCTOR,
/** 标明注解可以用于局部变量声明 */
LOCAL_VARIABLE,
/** 标明注解可以用于注解声明(应用于另一个注解上)*/
ANNOTATION_TYPE,
/** 标明注解可以用于包声明 */
PACKAGE,
/**
* 标明注解可以用于类型参数声明(1.8新加入)
*/
TYPE_PARAMETER,
/**
* 类型使用声明(1.8新加入)
*/
TYPE_USE
}
当注解未指定Target值时,则此注解可以用于任何元素之上,多个值使用{}包含并用逗号隔开,下面代码表示,此Annotation
既可以注解构造函数、字段和方法:
@Target(value={CONSTRUCTOR, FIELD, METHOD})
@Documented:将被标注的注解生成到javadoc中。
@Inherited:其让被修饰的注解拥有被继承的能力。如下,我们有一个用@Inherited修饰的注解@InAnnotation,那么这个注解就拥有了被继承的能力。
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InAnnotation{
}
@InAnnotation
class Base{}
class Son extends Base{}
当使用此注解修饰一个基类Base, 其子类Son 并没有使用任何注解修饰,但是其已经拥有了@InAnnotation这个注解,相当于Son 已经被@InAnnotation修饰了
@Repeatable :使被修饰的注解可以重复的注解某一个程序元素。例如下面的代码中@ShuSheng这个自定义注解使用了@Repeatable修饰,所以其可以按照下面的语法重复的注解一个类。是JDK1.8新加入的
@ShuSheng(name="frank",age=18)
@ShuSheng(age = 20)
public class AnnotationDemo{}
如何定义一个重复注解呢,如下所示,我们需要先定义一个容器,例如ShuShengs ,然后将其作为参数传入@Repeatable中。
@Repeatable(ShuShengs.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ShuSheng {
String name() default "ben";
int age();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ShuShengs {
ShuSheng[] value();
}
注解元素及其数据类型
通过上述对@Test注解的定义,我们了解了注解定义的过程,由于@Test内部没有定义其他元素,所以@Test也称为标记注解(marker annotation),但在自定义注解中,一般都会包含一些元素以表示某些值,方便处理器使用
/**
* Created by wuzejian on 2017/5/18.
* 对应数据表注解
*/
@Target(ElementType.TYPE)//只能应用于类上
@Retention(RetentionPolicy.RUNTIME)//保存到运行时
public @interface DBTable {
String name() default "";
}
上述定义一个名为DBTable的注解,该用于主要用于数据库表与Bean类的映射(稍后会有完整案例分析),与前面Test注解不同的是,我们声明一个String类型的name元素,其默认值为空字符,但是必须注意到对应任何元素的声明应采用方法的声明方式,同时可选择使用default提供默认值,@DBTable使用方式如下:
//在类上使用该注解
@DBTable(name = "MEMBER")
public class Member {
//.......
}
关于注解支持的元素数据类型除了上述的String,还支持如下数据类型
所有基本类型(int,float,boolean,byte,double,char,long,short)
String
Class
enum
Annotation
上述类型的数组
倘若使用了其他数据类型,编译器将会丢出一个编译错误,注意,声明注解元素时可以使用基本类型但不允许使用任何包装类型,同时还应该注意到注解也可以作为元素的类型,也就是嵌套注解,下面的代码演示了上述类型的使用过程:
package com.zejian.annotationdemo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by wuzejian on 2017/5/19.
* 数据类型使用Demo
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Reference{
boolean next() default false;
}
public @interface AnnotationElementDemo {
//枚举类型
enum Status {FIXED,NORMAL};
//声明枚举
Status status() default Status.FIXED;
//布尔类型
boolean showSupport() default false;
//String类型
String name()default "";
//class类型
Class<?> testCase() default Void.class;
//注解嵌套
Reference reference() default @Reference(next=true);
//数组类型
long[] value();
}
注意:元素必须要么具有默认值,要么在使用注解时提供元素的值,注解不支持继承
基本注解
Java内置的注解共有5个
Override:让编译器检查被标记的方法,保证其重写了父类的某一个方法。此注解只能标记方法。源码如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Deprecated:标记某些程序元素已经过时,程序员请不要再使用了。源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
@SuppressWarnings :告诉编译器不要给老子显示警告,老子不想看,老子清楚的知道自己在干什么。源码如下:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
其内部有一个String数组,根据传入的值来取消相应的警告:
deprecation:使用了不赞成使用的类或方法时的警告;
unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;
fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
path:在类路径、源文件路径等中有不存在的路径时的警告;
serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
finally:任何 finally 子句不能正常完成时的警告;
all:关于以上所有情况的警告。
@SafeVarargs(Java7 新增) :@SuppressWarnings可以用在各种需要取消警告的地方,而 @SafeVarargs主要用在取消参数的警告。就是说编译器如果检查到你对方法参数的操作,有可能发生问题时会给出警告,但是你很自(任)性,老子不要警告,于是你就加上了这个标签。源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}
其实这个注解是专为取消堆污染警告设置的,因为Java7会对可能产生堆污染的代码提出警告,什么是堆污染?且看下面代码
@SafeVarargs
private static void method(List<String>... strLists) {
List[] array = strLists;
List<Integer> tmpList = Arrays.asList(42);
array[0] = tmpList; //非法操作,但是没有警告
String s = strLists[0].get(0); //ClassCastException at runtime!
}
如果不使用 @SafeVarargs,这个方法在编译时候是会产生警告的 : “…使用了未经检查或不安全的操作。”,用了就不会有警告,但是在运行时会抛异常。
@FunctionalInterface(Java8 新增): 标记型注解,告诉编译器检查被标注的接口是否是一个函数接口,即检查这个接口是否只包含一个抽象方法,只有函数接口才可以使用Lambda表达式创建实例。源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
使用注解
Annotation接口是所有注解的父接口(需要通过反编译查看),在java.lang.reflect 反射包下存在一个叫AnnotatedElement接口,其表示程序中可以接受注解的程序元素,例如 类,方法,字段,构造函数,包等等。而Java为使用反射的主要类实现了此接口,如反射包内的Constructor类、Field类、Method类、Package类和Class类。
当我们通过反射技术获取到反射包内的那些类型的实例后,就可以使用AnnotatedElement接口的中的API方法来获取注解的信息了。
<T extends Annotation> T getAnnotation(Class<T> annotationClass); : 返回该元素上存在的指定类型的注解,如果不存在则返回 null。
default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass){} :返回该元素上存在的直接修饰该元素的指定类型的注解,如果不存在则返回null.
Annotation[] getAnnotations();:返回该元素上存在的所有注解。
Annotation[] getDeclaredAnnotations();:返回该元素上存在的直接修饰该元素的所有注解。
default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass){}:该方法功能与前面getAnnotation方法类似,但是由于Java8 加入了重复注解功能,因此需要此方法获取修饰该程序元素的指定类型的多个Annotation
获取注解简单示例
首先我们定义了两个注解@Master与@ShuSheng,@ShuSheng是一个可重复注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Master {
}
@Repeatable(ShuShengs.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ShuSheng {
String name() default "ben";
int age();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ShuShengs {
ShuSheng[] value();
}
使用定义好的注解来修饰,如下
@Master
public class AnoBase {
}
@ShuSheng(name="frank",age=18)
@ShuSheng(age = 20)
public class AnnotationDemo extends AnoBase{
}
调用相关函数获取相应的结果
private static void getAnnotation()
{
Class<?> cInstance=AnnotationDemo.class;
//获取AnnotationDemo上的重复注解
ShuSheng[] ssAons= cInstance.getAnnotationsByType(ShuSheng.class);
System.out.println("重复注解:"+Arrays.asList(ssAons).toString());
//获取AnnotationDemo上的所有注解,包括从父类继承的
Annotation[] allAno=cInstance.getAnnotations();
System.out.println("所有注解:"+Arrays.asList(allAno).toString());
//判断AnnotationDemo上是否存在Master注解
boolean isP=cInstance.isAnnotationPresent(Master.class);
System.out.println("是否存在Master: "+isP);
}
执行结果如下:
重复注解:[@top.ss007.ShuSheng(name=frank, age=18), @top.ss007.ShuSheng(name=ben, age=20)]
所有注解:[@top.ss007.ShuShengs(value=[@top.ss007.ShuSheng(name=frank, age=18), @top.ss007.ShuSheng(name=ben, age=20)])]
是否存在Master: false
自定义注解处理器(APT)
了解完注解与反射的相关API后,就可以更进一步。下面的实例自定义了一个APT,完成通过注解构建SQL语句的功能。此处代码来自此处。下面代码要求对数据库有初步认识。
先定义相关的注解
/**
* 用来注解表
*/
@Target(ElementType.TYPE)//只能应用于类上
@Retention(RetentionPolicy.RUNTIME)//保存到运行时
public @interface DBTable {
String name() default "";
}
/**
* 注解Integer类型的字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
//该字段对应数据库表列名
String name() default "";
//嵌套注解
Constraints constraint() default @Constraints;
}
/**
* 注解String类型的字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
//对应数据库表的列名
String name() default "";
//列类型分配的长度,如varchar(30)的30
int value() default 0;
Constraints constraint() default @Constraints;
}
/**
* 约束注解
*/
@Target(ElementType.FIELD)//只能应用在字段上
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
//判断是否作为主键约束
boolean primaryKey() default false;
//判断是否允许为null
boolean allowNull() default false;
//判断是否唯一
boolean unique() default false;
}
/**
* 数据库表Member对应实例类bean
*/
@DBTable(name = "MEMBER")
public class Member {
//主键ID
@SQLString(name = "ID",value = 50, constraint = @Constraints(primaryKey = true))
private String id;
@SQLString(name = "NAME" , value = 30)
private String name;
@SQLInteger(name = "AGE")
private int age;
@SQLString(name = "DESCRIPTION" ,value = 150 , constraint = @Constraints(allowNull = true))
private String description;//个人描述
//省略set get.....
}
上述定义4个注解,分别是@DBTable(用于类上)、@Constraints(用于字段上)、 @SQLString(用于字段上)、@SQLString(用于字段上)并在Member类中使用这些注解,这些注解的作用的是用于帮助注解处理器生成创建数据库表MEMBER的构建语句,在这里有点需要注意的是,我们使用了嵌套注解@Constraints,该注解主要用于判断字段是否为null或者字段是否唯一。接下来就需要编写我们自己的注解处理器了。
public class TableCreator {
public static String createTableSql(String className) throws ClassNotFoundException {
Class<?> cl = Class.forName(className);
DBTable dbTable = cl.getAnnotation(DBTable.class);
//如果没有表注解,直接返回
if(dbTable == null) {
System.out.println(
"No DBTable annotations in class " + className);
return null;
}
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>();
//通过Class类API获取到所有成员字段
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];
//获取字段对应列名称,如果没有就是使用字段名称替代
if(sInt.name().length() < 1)
columnName = field.getName().toUpperCase();
else
columnName = sInt.name();
//构建语句
columnDefs.add(columnName + " INT" +
getConstraints(sInt.constraint()));
}
//判断String类型
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.constraint()));
}
}
//数据库表构建语句
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) + ");";
return tableCreate;
}
/**
* 判断该字段是否有其他约束
* @param con
* @return
*/
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 {
String[] arg={"com.zejian.annotationdemo.Member"};
for(String className : arg) {
System.out.println("Table Creation SQL for " +
className + " is :\n" + createTableSql(className));
}
}
}
输出结果为:
Table Creation SQL for com.zejian.annotationdemo.Member is :
CREATE TABLE MEMBER(
ID VARCHAR(50) NOT NULL PRIMARY KEY,
NAME VARCHAR(30) NOT NULL,
AGE INT NOT NULL,
DESCRIPTION VARCHAR(150)
);