Java注解解析
一、元注解
元注解顾名思义我们可以理解为注解的注解,它是作用在注解中,方便我们使用注解实现想要的功能。元注解分别有@Retention、 @Target、 @Document、 @Inherited和@Repeatable(JDK1.8加入)五种。
1. @Retention
Retention英文意思有保留、保持的意思,它表示注解存在阶段是保留在源码(编译期),字节码(类加载)或者运行期(JVM中运行)。在@Retention注解中使用枚举RetentionPolicy来表示注解保留时期
- @Retention(RetentionPolicy.SOURCE),注解仅存在于源码中,在class字节码文件中不包含
- @Retention(RetentionPolicy.CLASS), 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
- @Retention(RetentionPolicy.RUNTIME), 注解会在class字节码文件中存在,在运行时可以通过反射获取到
如果我们是自定义注解,则通过前面分析,我们自定义注解如果只存着源码中或者字节码文件中就无法发挥作用,而在运行期间能获取到注解才能实现我们目的,所以自定义注解中肯定是使用 @Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTestAnnotation {
}
2. @Target
Target的英文意思是目标,这也很容易理解,使用@Target元注解表示我们的注解作用的范围就比较具体了,可以是类,方法,方法参数变量等,同样也是通过枚举类ElementType表达作用类型
- @Target(ElementType.TYPE) 作用接口、类、枚举、注解
- @Target(ElementType.FIELD) 作用属性字段、枚举的常量
- @Target(ElementType.METHOD) 作用方法
- @Target(ElementType.PARAMETER) 作用方法参数
- @Target(ElementType.CONSTRUCTOR) 作用构造函数
- @Target(ElementType.LOCAL_VARIABLE)作用局部变量
- @Target(ElementType.ANNOTATION_TYPE)作用于注解(@Retention注解中就使用该属性)
- @Target(ElementType.PACKAGE) 作用于包
- @Target(ElementType.TYPE_PARAMETER) 作用于类型泛型,即泛型方法、泛型类、泛型接口 (jdk1.8加入)
- @Target(ElementType.TYPE_USE) 类型使用.可以用于标注任意类型除了 class (jdk1.8加入)
一般比较常用的是ElementType.TYPE类型
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyTestAnnotation {
}
3. @Documented
Document的英文意思是文档。它的作用是能够将注解中的元素包含到 Javadoc 中去。
4. @Inherited
Inherited的英文意思是继承,但是这个继承和我们平时理解的继承大同小异,一个被@Inherited注解了的注解修饰了一个父类,如果他的子类没有被其他注解修饰,则它的子类也继承了父类的注解。下面我们来看个@Inherited注解例子
/**自定义注解*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyTestAnnotation {
}
/**父类标注自定义注解*/
@MyTestAnnotation
public class Father {
}
/**子类*/
public class Son extends Father {
}
/**测试子类获取父类自定义注解*/
public class test {
public static void main(String[] args){
//获取Son的class对象
Class<Son> sonClass = Son.class;
// 获取Son类上的注解MyTestAnnotation可以执行成功
MyTestAnnotation annotation = sonClass.getAnnotation(MyTestAnnotation.class);
}
}
5.@Repeatable
Repeatable的英文意思是可重复的。顾名思义说明被这个元注解修饰的注解可以同时作用一个对象多次,但是每次作用注解又可以代表不同的含义。
下面我们看一个人玩游戏的例子
/**
*一个人喜欢玩游戏,他喜欢玩英雄联盟,绝地求生,极品飞车,尘埃4等,
*则我们需要定义一个人的注解,他属性代表喜欢玩游戏集合,一个游戏注解,游戏属性代表游戏名称
*/
/**玩家注解*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface People {
Game[] value() ;
}
/**游戏注解*/
@Repeatable(People.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Game {
String value() default "";
}
/**玩游戏类*/
@Game(value = "LOL")
@Game(value = "PUBG")
@Game(value = "NFS")
@Game(value = "Dirt4")
public class PlayGame {
}
二、注解属性类型
注解属性类型可以有以下列出的类型
- 基本数据类型
- String
- 枚举类型
- 注解类型
- Class类型
- 以上类型的一维数组类型
三、获取注解属性
如果获取注解属性,当然是反射啦,主要有三个基本的方法
/**是否存在对应 Annotation 对象*/
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
return GenericDeclaration.super.isAnnotationPresent(annotationClass);
}
/**获取 Annotation 对象*/
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);
return (A) annotationData().annotations.get(annotationClass);
}
/**获取所有 Annotation 对象数组*/
public Annotation[] getAnnotations() {
return AnnotationParser.toArray(annotationData().annotations);
}
下面结合前面的例子,我们来获取一下注解属性,在获取之前我们自定义的注解必须使用元注解@Retention(RetentionPolicy.RUNTIME)
public class test {
public static void main(String[] args) throws NoSuchMethodException {
/**
* 获取类注解属性
*/
Class<Father> fatherClass = Father.class;
boolean annotationPresent = fatherClass.isAnnotationPresent(MyTestAnnotation.class);
if(annotationPresent){
MyTestAnnotation annotation = fatherClass.getAnnotation(MyTestAnnotation.class);
System.out.println(annotation.name());
System.out.println(annotation.age());
}
/**
* 获取方法注解属性
*/
try {
Field age = fatherClass.getDeclaredField("age");
boolean annotationPresent1 = age.isAnnotationPresent(Age.class);
if(annotationPresent1){
Age annotation = age.getAnnotation(Age.class);
System.out.println(annotation.value());
}
Method play = PlayGame.class.getDeclaredMethod("play");
if (play!=null){
People annotation2 = play.getAnnotation(People.class);
Game[] value = annotation2.value();
for (Game game : value) {
System.out.println(game.value());
}
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
四、注解实际应用
数据库字段数据唯一性校验注解
@Documented
@Retention(RUNTIME)
@Target({FIELD,PARAMETER})
@Constraint(validatedBy = {FieldDataValidator.class})
public @interface FieldDataValidate {
/**
* 数据库表名称,需要注意的是数据库表内必须存在is_del字段
*/
String tableName( );
/**
* 字段名称
*/
String fieldName( );
/**
* 返回信息
*/
String message( );
Class<?>[] groups( ) default {};
Class<? extends Payload>[] payload( ) default {};
}
数据库字段数据唯一性校验注解的处理
public class FieldDataValidator implements ConstraintValidator<FieldDataValidate, Object> {
@Resource
private FieldDataValidateMapper fieldDataValidateMapper;
/**
* 字段名称
*/
private String fieldName;
/**
* 数据库表名称
*/
private String tableName;
@Override
public void initialize(FieldDataValidate constraintAnnotation) {
this.fieldName = constraintAnnotation.fieldName();
this.tableName = constraintAnnotation.tableName();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (null == value) {
return true;
}
if (value instanceof String || value instanceof Integer || value instanceof Long) {
Boolean aBoolean = fieldDataValidateMapper.fieldDataValidate(tableName, fieldName, value);
return aBoolean == null ? false : aBoolean;
}
return false;
}
}
数据库字段查询
@Repository
public interface FieldDataValidateMapper {
/**
* 数据库字段数据唯一性校验
*
* @param tableName 数据库表名称
* @param fieldName 字段名称
* @param value 值
* @return 是否存在
*/
@Select("select 1 from ${tableName} where ${fieldName} = #{value} and is_del = 0 limit 1")
Boolean fieldDataValidate(@Param("tableName") String tableName,
@Param("fieldName") String fieldName,
@Param("value") Object value);
}
注解的使用
@Data
@ApiModel(value = "StationReqVo", description = "站点信息请求对象")
public class StationReqVo {
/**
* 主键
*/
@FieldDataValidate(tableName = "water_station", fieldName = "id", message = "站点不存在")
@ApiModelProperty("主键")
private Long id;
/**
* 站点名称
*/
@NotBlank(message = "站点名称不能为空")
@Size(max = 50, message = "站点名称最大50个字符")
@ApiModelProperty(value = "站点名称", required = true)
private String stationName;
/**
* 所在水体
*/
@FieldDataValidate(tableName = "rl_water_body", fieldName = "id", message = "水体不存在")
@NotNull(message = "请选择站点所在水体")
@ApiModelProperty(value = "所在水体", required = true)
private Long waterId;
}