Java自定义注解的实现
简介
注解:说明程序的,给计算机看的。
注释:用文字描述程序的,给程序员看的。
定义:注解(Annotation),也叫元数据,一种代码级别的说明,它是JDK1.5以后版本引入的一个特性,与类、接口、枚举在同一个层次。可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
概念描述:
- JDK1.5之后的新特性
- 用于说明程序
- 使用注解:@注解名称
作用分类:
- 编写文档:通过代码里标识的注解生成文档
- 代码分析:通过注解对代码进行分析(利用反射)
- 编译检查:通过代码里的注解让编译器能够实现基本的编译检查例如Override
JDK中预定义的一些注解
@Override
:检测被该注解标注的方法是否是继承自夫类/接口的@Deprecated
:表示该注解标注的内容,已过时@SuppressWarnings
:压制警告
自定义注解
注解的定义规则
观察预定义注解的源码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
可以看出,注解的定义方法是
@元注解
@元注解
....
public @interface 注解名{
}
其中,元注解可以省略,元注解的概念,我们下面再说。
注解的本质
模仿上面定义的方式,创建一个自定义注解
public @interface MyAnno{
}
使用javac
命令编译,查看源码:
public interface MyAnno extends java.lang.annotation.Annotation{}
所以注解本质上就是一个接口,其默认继承了Annotation接口
注解中的属性
注解本质就是一个接口,所以属性本质上就是接口中可以定义的成员方法。
接口里面能定义的,注解内都可以定义
属性返回值的要求:
- 基本数据类型
- String
- 枚举
- 注解
- 以上的数据类型
- 不能是void或者包装类型
下面是定义一个包含属性的注解的声明:
package cn.rayfoo.common.annotation.validate;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/7 11:19
*/
public @interface NotNull {
int myProp1();
String myProp2();
long[] myProp3();
}
此时,在使用注解的时候,必须给属性赋值
属性的修饰default
使用注解时,不需要给注解赋值,可以在属性后加default修饰。不加修饰的为必须指定的内容。
package cn.rayfoo.common.annotation.validate;
/**
* @author rayfoo@qq.com
* @version 1.0
* <p></p>
* @date 2020/8/7 11:19
*/
public @interface NotNull {
int myProp1() default 10;
String myProp2() default "rayfoo";
long[] value();
}
使用带有参数的注解
数字属性使用{}修饰
@NotNull(prop2 = "giao",value={1L,2L})
如果有且只有一个必须赋值的属性,并且属性名为value,属性的name可以省略
@NotNull(value={1L,2L})
数组中如果只有一个元素,大括号可以省略
@NotNull(value=1L})
带有注解类型属性的使用方式
@ApiImplicitParams({
@ApiImplicitParam(name = "name", value = "用户名", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "password", value = "密码", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "phoneNumber", value = "手机号", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "signature", value = "签名", required = false, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "email", value = "邮箱", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "code", value = "验证码", required = true, dataType = "String", paramType = "query"),
})
包含枚举属性的注解使用方式
@RequestMapping(value = "/user",method = HttpMethod.DELETE)
元注解
元注解就是用于描述注解的注解
@Target:描述注解能够作用的位置
下面时target的源码,其只有一个枚举类数组类型的属性ElementType[]
@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();
}
ElementType
取值
public enum ElementType {
/** 类和接口上 */
TYPE,
/** 字段上 */
FIELD,
/** 方法上 */
METHOD,
/** 参数上 */
PARAMETER,
/** 构造上 */
CONSTRUCTOR,
/** 本地变量上 */
LOCAL_VARIABLE,
/** 注解上 */
ANNOTATION_TYPE,
/** 包上 */
PACKAGE,
/**
* 类型参数上
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
这个可以根据自己的需要,添加合适的修饰。
@Retention:描述注解能够保留的阶段
观察源码,可以看出,其只有一个枚举类型的属性
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
RetentionPolicy
取值范围
public enum RetentionPolicy {
/**
* 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
*/
SOURCE,
/**
* 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
*/
CLASS,
/**
* 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;(自定义注解一般都用RUNTIME)
*/
RUNTIME
}
@Retention:描述注解能够保留的阶段
@Documented:描述注解是否能被抽取到API文档中
加上该注解后,其修饰的注解会被抽取到JavaDoc
文档中
@Inherited:描述注解是否可以被子类继承
加入此注解后继承了加了此注解修饰的类的子类,也会自动加上该注解修饰的注解
注解的扫描
加入注解后,对类没有任何的影响,真正进行操作的是读取注解处,我们可以借助反射中的知识,读取并借助注解的属性进行一些操作。一般配合AOP一起使用。
field.isAnnotationPresent(注解.class)
注解 verify = field.getAnnotation(注解.class);
String name = 注解.name();
...判断