首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 註解完整指南:內建註解、自訂註解、元註解、反射處理、Spring 實戰、編譯期處理 (APT)

     編輯:Java 相關知識

前言

Annotation (註解) 是 JDK 5 引入的元資料機制,已成為現代 Java 框架 (Spring、Hibernate、JUnit、MapStruct、Lombok) 的核心基礎。本文系統整理:三大內建註解、四大元註解、自訂註解語法、保留策略、目標範圍、反射執行期處理、編譯期處理 (Annotation Processor)、Spring 實戰整合、最佳實踐與避坑指南。

1. 三大內建註解 (java.lang)

註解用途範例
@Override標示方法覆寫父類/介面,編譯器檢查簽名一致@Override public String toString()
@Deprecated標示 API 過時,編譯器發出警告,配合 Javadoc @deprecated@Deprecated(since="2.0", forRemoval=true)
@SuppressWarnings抑制特定編譯器警告@SuppressWarnings({"unchecked", "rawtypes"})

2. 四大元註解 (java.lang.annotation)

元註解作用可選值預設
@Retention註解保留階段SOURCE (源碼)、CLASS (位元組碼、預設)、RUNTIME (執行期可反射)CLASS
@Target註解可標註的程式元素TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE (JDK 8+)所有
@Documented標註此元註解的註解會出現在 Javadoc 中無否
@Inherited子類別自動繼承父類別的此註解 (僅限 TYPE 目標)無否

3. 自訂註解語法

// 1. 基本註解 (無屬性)
public @interface Marker {}

// 2. 單一屬性 (慣例命名 value)
public @interface Priority {
    int value() default 0;
}

// 3. 多屬性、預設值、陣列
public @interface DbConfig {
    String driver() default "com.mysql.cj.jdbc.Driver";
    String url() default "jdbc:mysql://localhost:3306/db";
    String user() default "root";
    String password() default "";
    String[] schemas() default {}; // 陣列屬性
}

// 4. 完整元註解修飾
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Inherited
public @interface ApiEndpoint {
    String path();
    RequestMethod[] methods() default RequestMethod.GET;
    boolean secured() default true;
}

4. 反射執行期處理 (RUNTIME 保留)

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DbConfig {
    String driver() default "com.mysql.cj.jdbc.Driver";
    String url() default "jdbc:mysql://localhost:3306/db";
    String user() default "root";
    String password() default "";
}

public class DbUtil {
    @DbConfig(url = "jdbc:postgresql://pg:5432/app", user = "app")
    public static Connection getPgConnection() throws Exception {
        // 1. 取得方法上的註解
        Method method = DbUtil.class.getMethod("getPgConnection");
        DbConfig cfg = method.getAnnotation(DbConfig.class);
        // 2. 讀取屬性
        Class.forName(cfg.driver());
        return DriverManager.getConnection(cfg.url(), cfg.user(), cfg.password());
    }
}

進階:掃描類別路徑尋找註解 (需 ClassGraph/Reflections 庫)

ScanResult scan = new ClassGraph().enableAnnotationInfo().scan();
for (ClassInfo ci : scan.getClassesWithAnnotation(ApiEndpoint.class.getName())) {
    for (MethodInfo mi : ci.getMethodInfo()) {
        AnnotationInfo ai = mi.getAnnotationInfo(ApiEndpoint.class.getName());
        String path = ai.getParameterValues().get("path").asString();
        // 註冊路由...
    }
}

5. 編譯期處理:Annotation Processor (APT)

編譯期產生程式碼、資源檔、驗證約束,不影響執行期效能。Lombok、MapStruct、Room、Dagger 皆基於此。

處理器骨架

@SupportedAnnotationTypes("com.example.ApiEndpoint")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public class ApiEndpointProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {
        for (Element elem : roundEnv.getElementsAnnotatedWith(ApiEndpoint.class)) {
            if (elem.getKind() != ElementKind.METHOD) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@ApiEndpoint only on methods", elem);
                continue;
            }
            ExecutableElement method = (ExecutableElement) elem;
            ApiEndpoint ann = method.getAnnotation(ApiEndpoint.class);
            // 產生程式碼
            String className = elem.getEnclosingElement().getSimpleName() + "Router";
            TypeSpec router = TypeSpec.classBuilder(className)
                .addModifiers(Modifier.PUBLIC)
                .addMethod(MethodSpec.methodSpec("register")
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(TypeName.VOID)
                    .addStatement("System.out.println(\"Register $L\")", ann.path())
                    .build())
                .build();
            JavaFile.builder("com.example.generated", router).build().writeTo(processingEnv.getFiler());
        }
        return true; // 聲稱已處理
    }
}

註冊 (META-INF/services/javax.annotation.processing.Processor)

com.example.ApiEndpointProcessor

Maven 啟用

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <annotationProcessorPaths>
            <path>${project.groupId}:${project.artifactId}:${project.version}</path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

6. Spring 實戰整合

場景Spring 註解原理
元件掃描@Component, @Service, @Repository, @ControllerClassPathBeanDefinitionScanner 讀取 TYPE 註解
配置屬性綁定@ConfigurationProperties(prefix="app")ConfigurationPropertiesBindingPostProcessor 反射設值
條件裝配@ConditionalOnProperty, @ConditionalOnMissingBeanConditionEvaluator 執行期判斷
切面程式設計@Aspect, @Around, @PointcutAnnotationAwareAspectJAutoProxyCreator 建立 Proxy
事務管理@TransactionalTransactionInterceptor 環繞通知
驗證@Valid, @NotNull, @SizeMethodValidationInterceptor / Bean Validation
OpenAPI 文檔@Operation, @Parameter, @SchemaSpringDoc 掃描產生 OpenAPI Spec

7. 最佳實踐與避坑

  1. 保留策略選擇:源碼級驗證/產生碼用 SOURCE;框架掃描類別用 CLASS;執行期反射用 RUNTIME。
  2. 目標範圍最小化:只在必要元素上標註,減少掃描成本。
  3. 屬性型別:優先用基本型別、String、Class、Enum、註解、陣列;避免複雜物件。
  4. 預設值:盡量提供合理預設,減少使用者樣板碼。
  5. 命名衝突:自訂註解加專案前綴 (如 @MyAppConfig),避免與第三方衝突。
  6. 繼承陷阱:@Inherited 僅對 @Target(TYPE) 生效,方法/欄位註解不會繼承。
  7. 編譯期 vs 執行期:APT 產生的程式碼在編譯期即存在,IDE 需啟用「Annotation Processing」才能跳轉。
  8. 測試註解處理器:使用 Google Compile Testing 庫編譯測試專案,斷言產生碼/錯誤訊息。

8. 版本演進重點

  • JDK 8:@Repeatable (可重複註解)、TYPE_USE/TYPE_PARAMETER 目標 (支援泛型參數註解 List<@NonNull String>)。
  • JDK 9:模組系統 module-info.java 中宣告 uses/provides 處理器。
  • JDK 11+:javax.annotation.processing 移出 JDK,需額外依賴 javax.annotation:javax.annotation-api。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved