官方文档定义:注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。
简单来说,你可以理解为一种标签,例如一提到自己心中的女神,脑海中自然会为她贴上"beautiful"这个标签,但是你的这个"beautiful"标签并不属于她本身,你的这个标签对她来说没有直接影响,你只是条单纯的舔狗而已......
好吧,回到正题,注解存在的意义是什么呢?
注解的用处主要如下:
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
还是回到官方文档的解释上,注解主要针对的是编译器和其它工具软件(SoftWare tool)。
当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。
所以呢,注解是给编译器或APT用的
例如:
public @interface AnnotationTest() {
}
@AnnotationTest
public void Test() {
}
上述代码简单来说就是把AnnotationTest作为一个标签贴到Test类上面去了
但是实际上并没有什么卵用,因为根本没有实现注解功能
想要注解能够正常工作,还是要依赖于元注解这个东西
元注解是一种基本注解,可以注解到其他的注解上面去
主要有@Retention、@Document、@Target、@Inherited、@Repeatable5种
@Retention
当它应用到一个注解上的时候,说明了这个注解的存活时间
取值如下:
- RetentionPolicy.SOURCE: 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
- RetentionPolicy.CLASS: 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
- RetentionPolicy.RUNTIME: 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
@Retention(RetentionPolicy.CLASS)
public @interface AnnotationTest() {
}
@Document
将被标注的注解生成到javadoc中
@Target
用于指定被此元注解标注的注解可以标注的程序元素
可以看一下@Target的源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
可以看到有一个value属性,返回一个枚举ElementType类型的数组,这个数组的值就代表了可以使用的程序元素。
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
@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新加入),可以标注除class之外的任意类型
当注解未指定Target值时,则此注解可以用于任何元素之上,多个值使用{}包含并用逗号隔开
@Target(value = {ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.TYPE_PARAMETER})
@Inherited
其让被修饰的注解拥有被继承的能力,但是它并不是说注解本身可以继承,而是说如果一个父类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了父类的注解。
@Documented
@Retention(RetentionPolicy.CLASS)
@Target(value = {ElementType.TYPE})
@Inherited
public @interface TestAnnotation {}
@TestAnnotation
public class A {}
public class B extends A{}
也就是说B也拥有TestAnnotation 这个注解
@Repeatable
表示注解可以多次应用,通常是注解的值可以取多个
一个很简单的例子:我是一个程序员,同时也是美食家和摄影师
@interface Live {
People[] value();
}
@Repeatable(Live.class)
@interface People {
String role() default "";
}
@People(role = "programmer")
@People(role = "gastronome")
@People(role = "photographer")
public class Me {
}
上述代码通过@Repeatable注解了People ,而@Repeatable 后面括号中的Live类相当于一个容器注解(A fucking new concept)
容器注解:就是用来存放其它注解的地方,它本身也是一个注解
按java规定,容器注解里面必须要有一个value属性,属性类型是一个被 @Repeatable 注解过的注解数组
注解的属性也叫成员变量,注解只有成员变量,没有方法,而且成员变量在注解的定义中以“无形参的方法”的形式来声明,方法名定义了成员变量的名字,返回值定义了成员变量的类型。
在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组
注解中属性可以有默认值,默认值需要用 default 关键值指定,这样做的好处是可以省略赋值操作
@People()
public class Me {}
如果成员变量仅有一个名字叫做value的属性时,你甚至可以这样写
@People("programmer")
public class Me {}
如果没有成员变量的话,括号都可以省略哟!
@People
public class Me {}
Java语言本身也提供了预置的注解
@Deprecated
这个元素是用来标记过时的元素,会有一条横线,这是编译器识别后的提醒效果
@SuppressWarnings
阻止警告的意思。之前说过调用被 @Deprecated 注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过 @SuppressWarnings 达到目的。
@SafeVarargs
参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。
@FunctionalInterface
函数式接口注解,这个是 Java 1.8 版本引入的新特性。函数式编程很火,所以 Java 8 也及时添加了这个特性。
函数式接口 (Functional Interface) 就是一个具有一个方法的普通接口。
PS:函数式接口标记有什么用,这个原因是函数式接口可以很容易转换为 Lambda 表达式。
想必上面的理论知识都能很快理解吧,接下来就手撕代码吧
注解只是给我们想要的东西去贴上一个标签,那么怎么去阅读标签呢?
注解通过反射获取
查看一下Class.java源码,可以找到isAnnotationPresent()这个方法,用来判断它是否应用了某个注解
/**
* {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @since 1.5
*/
@Override
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
return GenericDeclaration.super.isAnnotationPresent(annotationClass);
}
上面代码可以看出Annotation接口是所有注解的父接口
此外,可以通过getAnnotation(Class<A> annotationClass())获取指定类型的Annotation对象
/**
* @throws NullPointerException {@inheritDoc}
* @since 1.5
*/
@SuppressWarnings("unchecked")
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);
return (A) annotationData().annotations.get(annotationClass);
}
而getAnnotations()可以获取到这个元素上的所有类型的Annotation对象
/**
* @since 1.5
*/
public Annotation[] getAnnotations() {
return AnnotationParser.toArray(annotationData().annotations);
}
getDeclaredAnnotation(Class<A> annotationClass)返回该元素上存在的直接修饰该元素的指定类型的注解,如果不存在则返回null.
/**
* @throws NullPointerException {@inheritDoc}
* @since 1.8
*/
@Override
@SuppressWarnings("unchecked")
public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);
return (A) annotationData().declaredAnnotations.get(annotationClass);
}
getDeclaredAnnotations()返回该元素上存在的直接修饰该元素的所有注解
/**
* @since 1.5
*/
public Annotation[] getDeclaredAnnotations() {
return AnnotationParser.toArray(annotationData().declaredAnnotations);
}
okay,枯燥无聊的源码还有很多,例如getDeclaredAnnotationsByType()、getAnnotationsByType()等等,接下来用一个简单的例子通过反射来获取Annotation的属性
首先,定义一个注解
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
public int id() default 1;
public String name() default "Kris";
}
然后呢
@TestAnnotation(id = 666,name = "Kris")
public class Me {
public static void main(String[] args) {
boolean hasAnnotation = Me.class.isAnnotationPresent(TestAnnotation.class);
if (hasAnnotation) {
TestAnnotation testAnnotation = Me.class.getAnnotation(TestAnnotation.class);
System.out.println("id:"+testAnnotation.id());
System.out.println("name:"+testAnnotation.name());
}
}
}
okay,接下来执行这个程序就可以发现:
Amazing!!!
那么,继续加把力呗!
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface A {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface B {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface C {
}
@TestAnnotation(id = 666,name = "Kris")
public class Me {
@A(value = "This is A!")
int a;
@B
@C
public void methodTest() {
}
public static void main(String[] args) {
boolean hasAnnotation = Me.class.isAnnotationPresent(TestAnnotation.class);
if (hasAnnotation) {
TestAnnotation testAnnotation = Me.class.getAnnotation(TestAnnotation.class);
System.out.println("id:"+testAnnotation.id());
System.out.println("name:"+testAnnotation.name());
}
try {
Field a = Me.class.getDeclaredField("a");
a.setAccessible(true);
A annotation = a.getAnnotation(A.class);
if (annotation != null) {
System.out.println("A value:"+annotation.value());
}
Method methodTest = Me.class.getDeclaredMethod("methodTest");
if (methodTest != null) {
Annotation[] ans = methodTest.getAnnotations();
for (int i=0;i<ans.length;i++) {
System.out.println("methodTest Annotation:"+ans[i].annotationType().getSimpleName());
}
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
显而易见的得到结果:
综上所述,只要通过给元素添加注解,再利用反射就可以获取注解啦,但是获取到有什么用呢?下面就通过数据库SQL创建语句实例来讲解一下APT(Annotation Process Tool)吧。
@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 false;
boolean unique() default false;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
String name() default "";
Constraints constraint() default @Constraints;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
String name() default "";
int value() default 0;
Constraints constraint() default @Constraints;
}
@DBTable(name = "MEMBER")
public class Member {
@SQLString(name = "ID",value = 50,constraint = @Constraints(primaryKey = true))
private int 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;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class TableCreator {
public static void main(String[] args) {
String[] arg={"AnnotationTest.SQLAnnotation.Member"};
for(String className : arg) {
System.out.println("Table Creation SQL for " +
className + " is :\n" + createTableSql(className));
}
}
private static String createTableSql(String className) {
Class<?> cl = null;
DBTable dbTable = null;
try {
cl = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if (cl != null) {
dbTable = cl.getAnnotation(DBTable.class);
}
if (dbTable == null) {
System.out.println("No DBTable Annotations int class!");
return null;
}
String tableName = dbTable.name();
if (tableName.length() == 0) {
tableName = cl.getName().toUpperCase();
}
List<String> columnDefs = new ArrayList<>();
for (Field field : cl.getDeclaredFields()) {
String columnName = null;
if (field.isAnnotationPresent(SQLInteger.class)) {
SQLInteger sqlInteger = (SQLInteger) field.getDeclaredAnnotation(SQLInteger.class);
if (sqlInteger.name().length() == 0) {
columnName = field.getName().toUpperCase();
} else {
columnName = sqlInteger.name();
}
columnDefs.add(columnName + " INT" + getConstraints(sqlInteger.constraint()));
} else if (field.isAnnotationPresent(SQLString.class)) {
SQLString sqlString = (SQLString) field.getDeclaredAnnotation(SQLString.class);
if (sqlString.name().length() == 0) {
columnName = field.getName().toUpperCase();
} else {
columnName = sqlString.name();
}
columnDefs.add(columnName+" VARCHAR(" + sqlString.value() + ")" + getConstraints(sqlString.constraint()));
}
}
StringBuilder createCommand = new StringBuilder("CREATE TABLE " + tableName + "(");
for (String columnDef : columnDefs) {
createCommand.append("\n " + columnDef + ",");
}
return createCommand.substring(0,createCommand.length()-1)+");";
}
private static String getConstraints(Constraints con) {
StringBuilder constraints = new StringBuilder();
if (!con.allowNull()) {
constraints.append(" NOT NULL");
}
if (con.primaryKey()) {
constraints.append(" PRIMARY KEY");
}
if (con.unique()) {
constraints.append(" UNIQUE");
}
return constraints.toString();
}
}
OK,这样就可以成功得通过APT得到所需要的SQL语句啦~