跳到主要内容

Java 注解属性

什么是注解属性

在Java中,注解(Annotation)不仅可以作为简单的标记存在,还可以携带数据。这些携带的数据就是通过注解属性来实现的。注解属性使得注解可以更加灵活地传递信息,使代码变得更加可配置和强大。

备注

注解属性有点类似于方法参数,允许我们在使用注解时提供额外的信息。

注解属性的基本语法

注解属性在定义注解时声明,语法类似于无参数方法:

java
public @interface AnnotationName {
数据类型 属性名() default 默认值;
}

属性类型限制

Java注解的属性类型必须是以下之一:

  • 基本数据类型(int, float, boolean等)
  • String类型
  • Class类型
  • 枚举类型
  • 注解类型
  • 以上类型的数组

定义带属性的注解

让我们看一个简单的例子,定义一个带有属性的注解:

java
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TestInfo {
// 属性声明
String author() default "Unknown";
String date();
int revision() default 1;
String[] comments() default {};
}

在这个例子中:

  • author() 属性有一个默认值 "Unknown"
  • date() 属性没有默认值,这意味着在使用注解时必须提供该属性值
  • revision() 属性的默认值是1
  • comments() 属性是一个字符串数组,默认为空数组

使用带属性的注解

使用带有属性的注解时,需要为没有默认值的属性提供值,也可以选择性地为有默认值的属性提供值:

java
public class AnnotationDemo {

@TestInfo(
author = "Alice",
date = "2023-10-20",
revision = 2,
comments = {"初始版本", "修复bug"}
)
public void testMethod() {
// 方法实现
System.out.println("测试方法执行");
}

@TestInfo(
date = "2023-10-21" // author和revision使用默认值
)
public void anotherMethod() {
// 方法实现
System.out.println("另一个方法执行");
}
}

特殊属性:value

如果一个注解只有一个属性,并且这个属性名为value,那么在使用注解时,可以省略属性名和等号,直接提供值:

java
// 定义只有value属性的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Description {
String value(); // 单一属性名为value
}

// 使用注解时可以简化
public class SimpleDemo {

@Description("这是一个测试方法") // 等同于 @Description(value = "这是一个测试方法")
public void testMethod() {
// 方法实现
}
}

注解属性的访问

通过反射API,我们可以在运行时访问注解的属性值:

java
import java.lang.reflect.Method;

public class AnnotationReaderDemo {

public static void main(String[] args) {
try {
// 获取AnnotationDemo类的testMethod方法
Method method = AnnotationDemo.class.getMethod("testMethod");

// 检查方法是否包含TestInfo注解
if (method.isAnnotationPresent(TestInfo.class)) {
// 获取注解实例
TestInfo testInfo = method.getAnnotation(TestInfo.class);

// 访问注解属性
System.out.println("作者: " + testInfo.author());
System.out.println("日期: " + testInfo.date());
System.out.println("修订版本: " + testInfo.revision());
System.out.println("备注: ");
for (String comment : testInfo.comments()) {
System.out.println("- " + comment);
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}

输出结果:

作者: Alice
日期: 2023-10-20
修订版本: 2
备注:
- 初始版本
- 修复bug

数组类型属性

当属性类型是数组时,有两种方式可以提供值:

java
// 方式1:使用大括号提供多个值
@TestInfo(
date = "2023-10-22",
comments = {"值1", "值2", "值3"}
)
public void method1() { }

// 方式2:如果只有一个值,可以省略大括号
@TestInfo(
date = "2023-10-22",
comments = "单个值" // 等同于 comments = {"单个值"}
)
public void method2() { }

实际应用场景

场景一:单元测试框架

java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
int timeout() default 0;
Class<? extends Throwable> expected() default None.class;

// 内部类表示无异常
static class None extends Throwable { }
}

public class UserServiceTest {

@Test(timeout = 1000) // 测试方法必须在1000毫秒内完成
public void testUserCreation() {
// 测试逻辑
}

@Test(expected = IllegalArgumentException.class) // 测试方法应当抛出指定异常
public void testInvalidUserInput() {
// 测试逻辑
}
}

场景二:配置信息注入

java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ConfigValue {
String value(); // 配置项的键名
String defaultValue() default ""; // 默认值
}

public class ApplicationConfig {

@ConfigValue("app.name")
private String applicationName;

@ConfigValue("app.version")
private String version;

@ConfigValue(value = "app.max-users", defaultValue = "100")
private int maxUsers;

// 配置加载器会在运行时使用反射读取这些注解
// 并从配置源中获取对应的值注入到这些字段中
}

场景三:自定义验证框架

java
// 字段长度校验注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Length {
int min() default 0;
int max() default Integer.MAX_VALUE;
String message() default "Field length must be between {min} and {max}";
}

// 字段不能为空校验注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface NotNull {
String message() default "Field cannot be null";
}

// 使用注解的实体类
public class User {

@NotNull(message = "用户名不能为空")
@Length(min = 4, max = 20, message = "用户名长度必须在4-20之间")
private String username;

@NotNull
@Length(min = 8, message = "密码长度不能少于8位")
private String password;

// getter和setter方法
}

总结

Java注解属性是使注解更加强大和灵活的重要机制,它们允许我们在使用注解时传递更多信息。通过合理设计注解属性,我们可以:

  1. 创建可配置的注解,适应不同的使用场景
  2. 为注解使用者提供清晰的接口,明确需要哪些信息
  3. 通过默认值减少必需的配置量,提高使用便利性

在实际开发中,注解属性被广泛应用于框架开发、代码生成、依赖注入和元数据处理等场景,是Java元编程的重要组成部分。

练习

  1. 创建一个名为@Column的注解,用于标记实体类的字段,包含以下属性:

    • name: 列名,String类型,默认为空字符串
    • nullable: 是否可以为空,boolean类型,默认为true
    • length: 长度,int类型,默认为255
  2. 创建一个名为@Controller的注解,用于标记控制器类,包含以下属性:

    • value: 路径前缀,String类型,无默认值
    • produces: 响应内容类型,String数组类型,默认为空数组
  3. 编写代码,通过反射获取第2题中@Controller注解的所有属性值并打印。

进阶学习

当你掌握了基本的注解属性后,可以进一步学习如何创建自定义注解处理器,以便在编译时或运行时处理这些注解信息。