在 Java 8 之前,同一个注解在同一位置只能使用一次。这种限制在某些场景下显得不够灵活,比如需要多个相同类型的配置时。重复注解(Repeated
Annotations)的引入,允许我们在同一个元素上多次使用相同的注解,极大地增强了注解的表达能力。

重复注解的设计哲学
重复注解的核心思想是向后兼容。Java 8 通过一个巧妙的机制实现了这一特性:编译器将重复注解转换为一个容器注解,这个容器注解包含重复注解的数组。

java
// 使用重复注解(Java 8+)
@Author(name = “Alice”)
@Author(name = “Bob”)
class Book {
// …
}

// 编译器将其转换为(Java 8之前的方式)
@Authors({
@Author(name = “Alice”),
@Author(name = “Bob”)
})
class Book {
// …
}
如何定义重复注解
创建一个重复注解需要两个步骤:

  1. 定义可重复的注解
    java
    import java.lang.annotation.*;

// 步骤1:定义可重复的注解
@Repeatable(Authors.class) // 指定容器注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Author {
String name();
String role() default “Author”;
int year() default 2024;
}

  1. 定义容器注解
    java
    // 步骤2:定义容器注解
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Authors {
    Author[] value(); // 必须命名为value,类型为可重复注解的数组
    }
    重复注解的完整示例
    java
    public class RepeatedAnnotationDemo {
    public static void main(String[] args) {
    // 1. 基本使用
    processBook(SimpleBook.class);

     // 2. 带参数的重复注解
     processBook(ComplexBook.class);
     
     // 3. 运行时访问
     accessAnnotationsAtRuntime();
     
     // 4. 与其他注解组合
     processService(MyService.class);
    

    }

    static void processBook(Class<?> bookClass) {
    System.out.println(“\n处理类: “ + bookClass.getSimpleName());

     // 获取重复注解(Java 8 新方式)
     Author[] authors = bookClass.getAnnotationsByType(Author.class);
     System.out.println("作者数量: " + authors.length);
     
     for (Author author : authors) {
         System.out.printf("  作者: %s (%s, %d)%n", 
                 author.name(), author.role(), author.year());
     }
     
     // 获取容器注解(传统方式,仍然可用)
     Authors authorsContainer = bookClass.getAnnotation(Authors.class);
     if (authorsContainer != null) {
         System.out.println("通过容器注解获取:");
         for (Author author : authorsContainer.value()) {
             System.out.printf("  作者: %s%n", author.name());
         }
     }
    

    }

    static void accessAnnotationsAtRuntime() {
    System.out.println(“\n=== 运行时访问注解 ===”);

     // 获取所有注解(包括容器注解)
     Annotation[] allAnnotations = BookWithHistory.class.getAnnotations();
     System.out.println("所有注解数量: " + allAnnotations.length);
     
     for (Annotation annotation : allAnnotations) {
         System.out.println("  注解类型: " + annotation.annotationType().getSimpleName());
         
         if (annotation instanceof Authors) {
             System.out.println("  这是一个容器注解");
         } else if (annotation instanceof Author) {
             System.out.println("  这是一个重复注解实例");
         }
     }
    

    }

    static void processService(Class<?> serviceClass) {
    System.out.println(“\n=== 与其他注解组合使用 ===”);

     if (serviceClass.isAnnotationPresent(Service.class)) {
         Service service = serviceClass.getAnnotation(Service.class);
         System.out.println("服务名称: " + service.name());
         System.out.println("服务版本: " + service.version());
     }
     
     Author[] authors = serviceClass.getAnnotationsByType(Author.class);
     System.out.println("贡献者: " + authors.length + " 人");
     
     for (Author author : authors) {
         System.out.println("  - " + author.name() + " (" + author.role() + ")");
     }
    

    }

    // 简单的重复注解使用
    @Author(name = “John Doe”)
    @Author(name = “Jane Smith”)
    static class SimpleBook {
    // 简单重复注解
    }

    // 带参数的重复注解
    @Author(name = “Alice”, role = “主编”, year = 2023)
    @Author(name = “Bob”, role = “技术审核”, year = 2023)
    @Author(name = “Charlie”, role = “测试”, year = 2024)
    static class ComplexBook {
    // 带不同参数的重复注解
    }

    // 历史书籍:多个版本作者
    @Author(name = “第一版作者”, year = 2010)
    @Author(name = “第二版修订者”, year = 2015)
    @Author(name = “第三版更新者”, year = 2020)
    static class BookWithHistory {
    // 展示注解的演进历史
    }

    // 重复注解与其他注解组合
    @Service(name = “UserService”, version = “2.0”)
    @Author(name = “架构师”, role = “架构设计”)
    @Author(name = “开发”, role = “编码实现”)
    @Author(name = “测试”, role = “质量保证”)
    static class MyService {
    // 服务类,有多个贡献者
    }
    }

// 服务注解定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Service {
String name();
String version();
}
重复注解在框架中的应用
重复注解在现代Java框架中得到了广泛应用:

  1. Spring框架中的重复注解
    java
    // Spring的组件扫描支持重复注解
    @Configuration
    @ComponentScan(basePackages = “com.example.service”)
    @ComponentScan(basePackages = “com.example.repository”)
    @ComponentScan(basePackages = “com.example.controller”)
    public class AppConfig {
    // 多包扫描配置
    }

// Spring Security的权限控制
@RestController
@RequestMapping(“/api/users”)
@PreAuthorize(“hasRole(‘ADMIN’)”)
@PreAuthorize(“hasPermission(‘user’, ‘read’)”)
public class UserController {
// 多个权限检查
}

  1. JAX-RS (RESTful Web Services)
    java
    @Path(“/books”)
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public class BookResource {

    @GET
    @Path(“/{id}”)
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) // 重复注解
    public Book getBook(@PathParam(“id”) Long id) {
    // 返回多种格式
    }

    @POST
    @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) // 重复注解
    public Response createBook(Book book) {
    // 接受多种格式
    }
    }

  2. 测试框架中的重复注解
    java
    // JUnit 5的重复测试注解
    @RepeatedTest(5) // 重复执行5次
    @DisplayName(“重复测试示例”)
    void repeatedTest() {
    // 测试逻辑
    }

// 自定义测试配置
@Test
@Tag(“integration”)
@Tag(“slow”)
@Timeout(value = 5, unit = TimeUnit.SECONDS)
void integrationTest() {
// 集成测试
}
自定义重复注解的实战案例
让我们创建一个完整的实战案例:一个文档生成系统,使用重复注解来标记文档的修订历史。

java
public class DocumentationSystem {
public static void main(String[] args) {
System.out.println(“=== 文档生成系统 ===\n”);

    // 处理API文档
    generateApiDocumentation(UserApi.class);
    
    // 处理工具类文档
    generateClassDocumentation(StringUtils.class);
    
    // 验证文档完整性
    validateDocumentation(PaymentService.class);
}

static void generateApiDocumentation(Class<?> apiClass) {
    System.out.println("生成API文档: " + apiClass.getSimpleName());
    
    // 获取API级别的文档
    if (apiClass.isAnnotationPresent(ApiDocumentation.class)) {
        ApiDocumentation apiDoc = apiClass.getAnnotation(ApiDocumentation.class);
        System.out.println("  API描述: " + apiDoc.description());
        System.out.println("  版本: " + apiDoc.version());
        System.out.println("  基础路径: " + apiDoc.basePath());
    }
    
    // 获取修订历史
    Revision[] revisions = apiClass.getAnnotationsByType(Revision.class);
    System.out.println("  修订历史 (" + revisions.length + " 次修订):");
    
    for (Revision rev : revisions) {
        System.out.printf("    v%s - %s (%s, %s)%n",
                rev.version(),
                rev.description(),
                rev.author(),
                rev.date());
    }
    
    // 获取方法级别的文档
    System.out.println("\n  方法列表:");
    for (Method method : apiClass.getDeclaredMethods()) {
        if (method.isAnnotationPresent(MethodDocumentation.class)) {
            MethodDocumentation methodDoc = 
                    method.getAnnotation(MethodDocumentation.class);
            System.out.printf("    %s(): %s%n",
                    method.getName(),
                    methodDoc.summary());
        }
    }
}

static void generateClassDocumentation(Class<?> utilClass) {
    System.out.println("\n生成工具类文档: " + utilClass.getSimpleName());
    
    // 获取作者信息
    Author[] authors = utilClass.getAnnotationsByType(Author.class);
    System.out.println("  作者 (" + authors.length + " 人):");
    
    for (Author author : authors) {
        System.out.printf("    %s (%s)%n",
                author.name(),
                author.email());
    }
    
    // 获取代码审查记录
    CodeReview[] reviews = utilClass.getAnnotationsByType(CodeReview.class);
    System.out.println("  代码审查记录:");
    
    for (CodeReview review : reviews) {
        System.out.printf("    %s: %s - %s%n",
                review.reviewer(),
                review.status(),
                review.comments());
    }
}

static void validateDocumentation(Class<?> serviceClass) {
    System.out.println("\n验证文档完整性: " + serviceClass.getSimpleName());
    
    // 检查必须有文档注解
    if (!serviceClass.isAnnotationPresent(ClassDocumentation.class)) {
        System.out.println("  ❌ 缺少类级别文档");
        return;
    }
    
    // 检查必须有至少一个作者
    Author[] authors = serviceClass.getAnnotationsByType(Author.class);
    if (authors.length == 0) {
        System.out.println("  ⚠️  没有指定作者");
    } else {
        System.out.println("  ✅ 作者: " + authors.length + " 人");
    }
    
    // 检查方法文档完整性
    int documentedMethods = 0;
    int totalMethods = 0;
    
    for (Method method : serviceClass.getDeclaredMethods()) {
        totalMethods++;
        if (method.isAnnotationPresent(MethodDocumentation.class)) {
            documentedMethods++;
        }
    }
    
    double coverage = (double) documentedMethods / totalMethods * 100;
    System.out.printf("  方法文档覆盖率: %.1f%% (%d/%d)%n",
            coverage, documentedMethods, totalMethods);
    
    if (coverage < 80) {
        System.out.println("  ⚠️  文档覆盖率不足");
    } else {
        System.out.println("  ✅ 文档覆盖率良好");
    }
}

// ===== 注解定义 =====

// 可重复的修订注解
@Repeatable(Revisions.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@interface Revision {
    String version();
    String description();
    String author();
    String date();
}

// 修订容器注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@interface Revisions {
    Revision[] value();
}

// 可重复的作者注解
@Repeatable(Authors.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Author {
    String name();
    String email();
}

// 作者容器注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Authors {
    Author[] value();
}

// 可重复的代码审查注解
@Repeatable(CodeReviews.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface CodeReview {
    String reviewer();
    String status(); // PASSED, NEEDS_WORK, etc.
    String comments();
}

// 代码审查容器注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface CodeReviews {
    CodeReview[] value();
}

// 其他文档注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface ApiDocumentation {
    String description();
    String version();
    String basePath();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface ClassDocumentation {
    String description();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface MethodDocumentation {
    String summary();
    String[] parameters() default {};
    String returns() default "";
}

// ===== 示例类定义 =====

@ApiDocumentation(
    description = "用户管理API",
    version = "1.2.0",
    basePath = "/api/v1/users"
)
@Revision(
    version = "1.0.0",
    description = "初始版本",
    author = "Alice",
    date = "2023-01-15"
)
@Revision(
    version = "1.1.0",
    description = "增加分页支持",
    author = "Bob",
    date = "2023-03-20"
)
@Revision(
    version = "1.2.0",
    description = "添加批量操作",
    author = "Charlie",
    date = "2023-06-10"
)
class UserApi {
    
    @MethodDocumentation(
        summary = "获取用户列表",
        parameters = {"page - 页码", "size - 每页大小"}
    )
    public void getUsers(int page, int size) {
        // 实现
    }
    
    @MethodDocumentation(
        summary = "创建新用户",
        parameters = {"user - 用户数据"},
        returns = "创建的用户ID"
    )
    public long createUser(Object user) {
        return 1L;
    }
}

@ClassDocumentation(description = "字符串工具类")
@Author(name = "David", email = "david@example.com")
@Author(name = "Eve", email = "eve@example.com")
@CodeReview(
    reviewer = "Frank",
    status = "PASSED",
    comments = "代码清晰,建议增加更多异常处理"
)
@CodeReview(
    reviewer = "Grace",
    status = "PASSED",
    comments = "性能良好,API设计合理"
)
class StringUtils {
    // 工具方法
}

@ClassDocumentation(description = "支付服务")
@Author(name = "支付团队", email = "payment@example.com")
class PaymentService {
    
    public void processPayment() {
        // 实现
    }
    
    @MethodDocumentation(summary = "退款处理")
    public void refund() {
        // 实现
    }
}

}
重复注解的底层原理
理解重复注解的底层原理有助于更好地使用它:

java
public class RepeatedAnnotationInternals {
public static void main(String[] args) throws Exception {
System.out.println(“=== 重复注解的底层原理 ===\n”);

    // 获取编译后的类字节码信息
    Class<TestClass> clazz = TestClass.class;
    
    System.out.println("1. 注解在字节码中的表示:");
    Annotation[] annotations = clazz.getAnnotations();
    for (Annotation ann : annotations) {
        System.out.println("  - " + ann.annotationType().getName());
    }
    
    System.out.println("\n2. 使用反射API的区别:");
    
    // getAnnotation() - 返回容器注解
    Authors authorsContainer = clazz.getAnnotation(Authors.class);
    System.out.println("getAnnotation(Authors.class): " + 
            (authorsContainer != null ? "找到容器注解" : "未找到"));
    
    // getAnnotationsByType() - 返回重复注解数组
    Author[] authors = clazz.getAnnotationsByType(Author.class);
    System.out.println("getAnnotationsByType(Author.class): " + 
            authors.length + " 个重复注解");
    
    System.out.println("\n3. 注解的继承关系:");
    Class<?> superClass = DerivedClass.class.getSuperclass();
    System.out.println(DerivedClass.class.getSimpleName() + 
            " 继承自 " + superClass.getSimpleName());
    
    // 重复注解是否继承?
    Author[] inheritedAuthors = DerivedClass.class.getAnnotationsByType(Author.class);
    System.out.println("继承的重复注解数量: " + inheritedAuthors.length);
    
    System.out.println("\n4. 注解保留策略的影响:");
    checkRetentionPolicies();
}

static void checkRetentionPolicies() {
    System.out.println("源代码级别注解:");
    SourceLevelAnnotation[] sourceAnns = 
            TestClass.class.getAnnotationsByType(SourceLevelAnnotation.class);
    System.out.println("  运行时获取数量: " + sourceAnns.length);
    
    System.out.println("\n类级别注解:");
    ClassLevelAnnotation[] classAnns = 
            TestClass.class.getAnnotationsByType(ClassLevelAnnotation.class);
    System.out.println("  运行时获取数量: " + classAnns.length);
    
    System.out.println("\n运行时注解:");
    RuntimeAnnotation[] runtimeAnns = 
            TestClass.class.getAnnotationsByType(RuntimeAnnotation.class);
    System.out.println("  运行时获取数量: " + runtimeAnns.length);
}

// 测试类
@Author(name = "Primary Author")
@Author(name = "Secondary Author")
@SourceLevelAnnotation("source")
@SourceLevelAnnotation("only")
@ClassLevelAnnotation("class")
@ClassLevelAnnotation("level")
@RuntimeAnnotation("runtime")
@RuntimeAnnotation("accessible")
static class TestClass {
}

static class DerivedClass extends TestClass {
}

// 不同保留策略的注解
@Repeatable(SourceLevelAnnotations.class)
@Retention(RetentionPolicy.SOURCE)  // 仅源代码
@Target(ElementType.TYPE)
@interface SourceLevelAnnotation {
    String value();
}

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@interface SourceLevelAnnotations {
    SourceLevelAnnotation[] value();
}

@Repeatable(ClassLevelAnnotations.class)
@Retention(RetentionPolicy.CLASS)  // 类文件
@Target(ElementType.TYPE)
@interface ClassLevelAnnotation {
    String value();
}

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
@interface ClassLevelAnnotations {
    ClassLevelAnnotation[] value();
}

@Repeatable(RuntimeAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)  // 运行时
@Target(ElementType.TYPE)
@interface RuntimeAnnotation {
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface RuntimeAnnotations {
    RuntimeAnnotation[] value();
}

}
重复注解的最佳实践
✅ 最佳实践:
合理使用场景:仅在真正需要多个相同注解时使用

保持一致性:所有重复注解应该具有相似的结构和用途

提供容器注解:即使不使用重复语法,容器注解也能保证兼容性

明确命名规范:容器注解的value方法必须返回重复注解的数组

❌ 常见陷阱:
忘记@Repeatable注解:

java
// 错误:缺少@Repeatable
@Retention(RetentionPolicy.RUNTIME)
@interface Tag {
String value();
}

// 使用时编译错误
// @Tag(“A”) @Tag(“B”) // 编译错误
容器注解不匹配:

java
// 错误:容器注解value方法类型不匹配
@Repeatable(Tags.class)
@interface Tag {
String value();
}

@interface Tags {
Tag[] tags(); // 错误:应该命名为value
}
忽略保留策略:

java
// 错误:重复注解和容器注解保留策略不一致
@Repeatable(RuntimeTags.class)
@Retention(RetentionPolicy.RUNTIME)
@interface Tag { /* … */ }

@Retention(RetentionPolicy.CLASS) // 错误:应该也是RUNTIME
@interface RuntimeTags { /* … */ }
重复注解与元注解的结合
重复注解可以与其他元注解结合,创建强大的注解系统:

java
public class MetaAnnotationCombination {
public static void main(String[] args) {
// 处理带有约束的注解
processConstrainedClass(ValidatedEntity.class);

    // 处理带有条件的注解
    processConditionalClass(FeatureToggledService.class);
}

static void processConstrainedClass(Class<?> entityClass) {
    System.out.println("处理约束类: " + entityClass.getSimpleName());
    
    // 获取所有约束
    Constraint[] constraints = entityClass.getAnnotationsByType(Constraint.class);
    
    for (Constraint constraint : constraints) {
        System.out.println("  约束: " + constraint.type() + 
                " - " + constraint.message());
        
        // 检查约束条件
        if (!evaluateConstraint(constraint)) {
            System.out.println("  ❌ 约束未满足: " + constraint.message());
        }
    }
}

static void processConditionalClass(Class<?> serviceClass) {
    System.out.println("\n处理条件类: " + serviceClass.getSimpleName());
    
    // 检查环境条件
    Environment[] envs = serviceClass.getAnnotationsByType(Environment.class);
    String currentEnv = System.getProperty("app.env", "development");
    
    boolean shouldLoad = true;
    for (Environment env : envs) {
        if (env.value().equals(currentEnv)) {
            System.out.println("  ✅ 环境匹配: " + currentEnv);
        } else {
            System.out.println("  ⚠️  环境不匹配: 需要 " + env.value() + 
                    ", 当前 " + currentEnv);
            if (env.required()) {
                shouldLoad = false;
            }
        }
    }
    
    System.out.println("  是否加载: " + (shouldLoad ? "是" : "否"));
}

static boolean evaluateConstraint(Constraint constraint) {
    // 模拟约束评估
    return Math.random() > 0.3;
}

// ===== 高级注解定义 =====

// 可重复的约束注解
@Repeatable(Constraints.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@interface Constraint {
    String type();  // NOT_NULL, UNIQUE, MIN, MAX, etc.
    String message();
    String value() default "";
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@interface Constraints {
    Constraint[] value();
}

// 可重复的环境条件注解
@Repeatable(Environments.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Environment {
    String value();  // development, test, production
    boolean required() default true;
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Environments {
    Environment[] value();
}

// ===== 示例类 =====

@Constraint(type = "NOT_NULL", message = "名称不能为空")
@Constraint(type = "MIN_LENGTH", message = "名称至少3个字符", value = "3")
@Constraint(type = "MAX_LENGTH", message = "名称最多50个字符", value = "50")
@Constraint(type = "PATTERN", message = "名称只能包含字母", value = "^[a-zA-Z]+$")
class ValidatedEntity {
    private String name;
    // 实体字段
}

@Environment("development")
@Environment(value = "test", required = false)
@Environment("production")
class FeatureToggledService {
    // 只在特定环境加载的服务
}

}
总结
重复注解是 Java 8 中一个看似简单但非常实用的特性,它:

增强表达能力:允许在同一位置使用多个相同类型的注解

保持向后兼容:通过容器注解机制与旧版本兼容

简化框架设计:使框架配置更加灵活和直观

促进代码清晰:用注解明确表达多重约束或配置

关键要点:

使用 @Repeatable 元注解标记可重复注解

必须提供对应的容器注解,其中包含返回重复注解数组的 value() 方法

通过 getAnnotationsByType() 方法获取重复注解数组

重复注解和容器注解应该有相同的保留策略和目标

重复注解特别适用于以下场景:

配置多个相同类型的值(如多个包扫描路径)

记录历史或版本信息(如修订记录)

添加多个约束或验证规则

指定多个环境或条件

掌握重复注解的使用,能让你的代码和框架设计更加灵活和强大,特别是在需要表达多重同类型信息时,它提供了一种优雅而标准的解决方案。