什么是Java运行期注解?

  • Post category:Java

Java运行期注解是一种在程序运行期间可被读取的注释,可以用来在程序运行的时候动态地完成一些特定的操作。它可以被用来在运行时处理数据、解析交互、验证输入参数等等。

下面让我们详细了解一下Java运行期注解的使用攻略:

  1. 定义注解

使用注解需要先定义注解。定义注解需要使用@来修饰一个interface,这个interface中的成员变量被称为注解元素。注解元素可以有默认值。

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "";
}

其中,@Retention(RetentionPolicy.RUNTIME) 是指设置注解保留到运行时。

  1. 使用注解

Java运行期注解可以依靠反射机制读取注解信息,以此来达到一定的目的。

首先,在代码中使用定义好的注解:

@MyAnnotation(value = "hello")
public class MyClass {
    // .....
}

然后,使用反射机制读取注解:

MyClass myClass = new MyClass();
Class clazz = myClass.getClass();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
    MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
    if (annotation != null) {
        String value = annotation.value();
        System.out.println(value);
    }
}

以上例子中,我们使用反射获取到 MyClass 类中的所有方法,然后使用 getAnnotation() 方法获取其方法上的 MyAnnotation 注解信息,并读取出其 value 值。如果 value 值不为空,则输出。

  1. 示例(1):自定义Spring MVC 控制器方法的注解

下面我们举个例子,实现自定义 Spring MVC 控制器方法的注解,以此来实现对请求参数的验证。以下是MyParam注解:

@Retention(RetentionPolicy.RUNTIME)
public @interface MyParam {
    // 参数名称
    String name();
    // 是否必填
    boolean required() default false;
    // 参数长度
    int length() default -1;
    // 范围
    String[] range() default {};
}

在控制器方法中使用MyParam注解:

@RequestMapping("/user/create")
@ResponseBody
public String createUser(@MyParam(name = "name", required = true, length = 20, range = {"a", "b", "c"}) String name,
                          @MyParam(name = "age", required = true, range = {"1", "100"}) int age) {
    // some code
    return "success";
}

使用反射机制获取方法参数上的注解并根据注解信息验证请求参数:

Object[] args = joinPoint.getArgs();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Annotation[][] annotations = method.getParameterAnnotations();
for (int i = 0; i < annotations.length; i++) {
    Annotation[] anns = annotations[i];
    Object arg = args[i];
    if (arg == null) {
        MyParam myParam = method.getParameters()[i].getAnnotation(MyParam.class);
        if (myParam.required()) {
            throw new IllegalArgumentException(myParam.name() + " is required");
        }
    } else {
        for (Annotation annotation : anns) {
            if (annotation instanceof MyParam) {
                MyParam myParam = (MyParam) annotation;
                checkParam(arg, myParam);
            }
        }
    }
}

使用上述代码,当请求的参数不符合注解中的规定时,会抛出相应的异常。

  1. 示例(2):自定义ORM框架

下面我们举一个更为复杂的例子,实现一个简单的ORM框架。 在我们的ORM框架中,实体类上会使用注解来表明其对应的数据库表以及属性与列的映射关系。

我们来看一下实体类上的注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    // 表名
    String name();
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    // 列名
    String name();
    // 是否为主键
    boolean primaryKey() default false;
}

在实体类中,通过使用Table注解来设置数据表名,在属性上使用Column注解来指定该属性对应的列。

@Table(name = "t_user")
public class User {
    @Column(name = "id", primaryKey = true)
    private Long id;
    @Column(name = "username")
    private String username;
    @Column(name = "password")
    private String password;
}

下面,我们使用反射机制读取注解信息, 自动生成SQL语句并执行。

public void insert(Object obj) {
    Class<?> clazz = obj.getClass();
    Table table = clazz.getAnnotation(Table.class);
    if (table == null) {
        throw new RuntimeException("Can not get table name");
    }
    StringBuilder sb = new StringBuilder();
    sb.append("INSERT INTO ").append(table.name()).append("(");
    Field[] fields = clazz.getDeclaredFields();
    List<Object> params = new ArrayList<>();
    for (Field field : fields) {
        Column column = field.getAnnotation(Column.class);
        if (column != null) {
            sb.append(column.name()).append(",");
            field.setAccessible(true);
            try {
                Object value = field.get(obj);
                params.add(value);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
    sb.deleteCharAt(sb.length() - 1).append(") VALUES(");
    for (int i = 0; i < params.size(); i++) {
        sb.append("?,");
    }
    sb.deleteCharAt(sb.length() - 1).append(")");
    try (PreparedStatement pstmt = getConnection().prepareStatement(sb.toString())) {
        for (int i = 0; i < params.size(); i++) {
            pstmt.setObject(i + 1, params.get(i));
        }
        pstmt.executeUpdate();
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

使用这个ORM框架,我们可以像以下的方式来插入数据:

User user = new User();
user.setUsername("test");
user.setPassword("123456");
ORMFramework orm = new ORMFramework();
orm.insert(user);

总结:Java运行期注解非常灵活,可以用来完成很多任务。了解Java运行期注解,可以帮助我们更好地设计程序结构,提高程序的可维护性和可扩展性。