Java Core 「5」自定义注解编程

216 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

01-元注解

定义:可以加在注解上的注解

@Document用于制作文档;

@Target描述注解的作用范围,即限制注解可以加在什么位置,例如加在属性、方法、包上等;

  • TYPE,只能在类、接口和枚举类型上使用
  • FIELD,只能在属性上使用
  • METHOD,只能在方法上使用

@Retention用于定义注解的保留策略;

  • SOURCE,表示注解只保留在*.java中,编译成*.class文件后丢弃;
  • CLASS,表示注解会保留到*.class文件中,当类加载到内存中时,注解丢失;通过 javap 查看 class 文件的字节码,可以发现,此种类型的注解表示为 RuntimeInvisibleAnnotations :
public void classPolicy();
  flags: ACC_PUBLIC
  Code:
    stack=0, locals=1, args_size=1
  	  0: return
    LineNumberTable:
  	line 11: 0
  RuntimeInvisibleAnnotations:
    0: #11()

RUNTIME,表示注解会保留到运行时,只有这种类型的注解才能够被反射机制读取到;通过 javap 查看 class 文件的字节码,可以发现,此种类型的注解表示为 RuntimeVisibleAnnotations :

public void runtimePolicy();
  flags: ACC_PUBLIC
  Code:
    stack=0, locals=1, args_size=1
      0: return
    LineNumberTable:
      line 15: 0
  RuntimeVisibleAnnotations:
    0: #14()

02-注解中属性的类型

定义注解时,可用的属性类型如下:

  • 8中基本类型,byte/short/int/long/float/double/boolean/char
  • String
  • Enum
  • Class
  • 注解
  • 上述类型的一维数组

当注解中的属性只有一个,并且名为value时,使用注解时,可以不指定属性名,默认为value赋值;当注解中的属性有多个,使用注解时,不论是否为value赋值,都必须指定属性名,表明一一对应关系;使用注解时,若属性为一维数组,且值只有一个,则可以省略{}

03-注解与反射接口

定义注解之后,如何读取注解中的内容呢?

java.lang.reflect 包中 AnnotatedElement 接口定义了与注解相关的方法:

/** 判断是否具备特定的注解 */
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
/** 返回特定类型的注解(若有),否则 null */
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
/** 获得所有的注解 */
Annotation[] getAnnotations();
/** 获得特定类型的注解 */
<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass);
/** 获得直接出现的注解 */
default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass);
/** 获得直接出现的、特定类型的注解 */
<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass);
Annotation[] getDeclaredAnnotations();

java.lang.Class、java.lang.reflect.Method、java.lang.reflect.Field等都实现了 AnnotatedElement 接口。

04-自定义注解编程

通过上面的介绍,可以发现,自定义注解编程,基本上分为三个步骤:1)使用元注解定义自定义注解;2)在编程中使用自定义注解;3)编写读取注解并处理的逻辑。

04.1-注解编程基本原理

  • 注解定义,@interface
  • 注解使用,@MyAnnotation
  • 注解读取

04.2-注解编程示例

根据注解自动生成SQL查询语句

定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
    String value();
}

使用注解

@Table("student")
public class StudentInfo {

    @Column("STUDENT_NAME")
    private String name;
    @Column("STUDENT_AGE")
    private int age;
    @Column("STUDENT_SEX")
    private int sex;

		// ...getter
		// ...setter
}

读取注解

@Test
public void testCase1() {

    StringBuilder stringBuilder = new StringBuilder();

    StudentInfo studentInfo = new StudentInfo();
    studentInfo.setName("张三");
    studentInfo.setAge(18);
    studentInfo.setSex(1);

    Class<?> clazz = studentInfo.getClass();
    if (!clazz.isAnnotationPresent(Table.class)) {
        System.err.println("StudentInfo.class上不包含@Table注解");
        return ;
    }

    Table table = clazz.getAnnotation(Table.class);
    String tableName = table.value();
    stringBuilder.append(String.format("SELECT * FROM %s WHERE 1=1", tableName));

    for (Field field : clazz.getDeclaredFields()) {
        if (!field.isAnnotationPresent(Column.class)) {
            System.out.println(String.format("%s is not annotated by @Column", field.getName()));
            continue;
        }
        
        Column column = field.getAnnotation(Column.class);
        String columnName = column.value();
        
        try {
            Method getter = clazz.getMethod(String.format("get%s", TableColumnTest.captureName(field.getName())));
            Object value = getter.invoke(studentInfo);

            if (value instanceof String) {
                stringBuilder.append(String.format(" AND %s = '%s'", columnName, value));
            } else if (value instanceof Integer) {
                stringBuilder.append(String.format(" AND %s = %s", columnName, value));
            } else {
                System.err.println("不支持的变量类型");
            }

        } catch (NoSuchMethodException nse) {

        } catch (IllegalAccessException | InvocationTargetException e) {

        }
    }

    System.out.println(stringBuilder.toString());
}

public static String captureName(String str) {
    char[] chars = str.toCharArray();
    chars[0] -= 32;
    return String.valueOf(chars);
}

输出结果为:

SELECT * FROM student WHERE 1=1 AND STUDENT_NAME = '张三' AND STUDENT_AGE = 18 AND STUDENT_SEX = 1

[1] java注解的本质以及注解的底层实现原理

[2] Java注解处理器


历史文章