Annotation (註解) 是 JDK 5 引入的元資料機制,已成為現代 Java 框架 (Spring、Hibernate、JUnit、MapStruct、Lombok) 的核心基礎。本文系統整理:三大內建註解、四大元註解、自訂註解語法、保留策略、目標範圍、反射執行期處理、編譯期處理 (Annotation Processor)、Spring 實戰整合、最佳實踐與避坑指南。
| 註解 | 用途 | 範例 |
|---|---|---|
@Override | 標示方法覆寫父類/介面,編譯器檢查簽名一致 | @Override public String toString() |
@Deprecated | 標示 API 過時,編譯器發出警告,配合 Javadoc @deprecated | @Deprecated(since="2.0", forRemoval=true) |
@SuppressWarnings | 抑制特定編譯器警告 | @SuppressWarnings({"unchecked", "rawtypes"}) |
| 元註解 | 作用 | 可選值 | 預設 |
|---|---|---|---|
@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 目標) | 無 | 否 |
// 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;
}
@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();
// 註冊路由...
}
}
編譯期產生程式碼、資源檔、驗證約束,不影響執行期效能。Lombok、MapStruct、Room、Dagger 皆基於此。
@SupportedAnnotationTypes("com.example.ApiEndpoint")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public class ApiEndpointProcessor extends AbstractProcessor {
@Override
public boolean process(Set extends TypeElement> 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; // 聲稱已處理
}
}
com.example.ApiEndpointProcessor
<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>
| 場景 | Spring 註解 | 原理 |
|---|---|---|
| 元件掃描 | @Component, @Service, @Repository, @Controller | ClassPathBeanDefinitionScanner 讀取 TYPE 註解 |
| 配置屬性綁定 | @ConfigurationProperties(prefix="app") | ConfigurationPropertiesBindingPostProcessor 反射設值 |
| 條件裝配 | @ConditionalOnProperty, @ConditionalOnMissingBean | ConditionEvaluator 執行期判斷 |
| 切面程式設計 | @Aspect, @Around, @Pointcut | AnnotationAwareAspectJAutoProxyCreator 建立 Proxy |
| 事務管理 | @Transactional | TransactionInterceptor 環繞通知 |
| 驗證 | @Valid, @NotNull, @Size | MethodValidationInterceptor / Bean Validation |
| OpenAPI 文檔 | @Operation, @Parameter, @Schema | SpringDoc 掃描產生 OpenAPI Spec |
SOURCE;框架掃描類別用 CLASS;執行期反射用 RUNTIME。@MyAppConfig),避免與第三方衝突。@Inherited 僅對 @Target(TYPE) 生效,方法/欄位註解不會繼承。Google Compile Testing 庫編譯測試專案,斷言產生碼/錯誤訊息。@Repeatable (可重複註解)、TYPE_USE/TYPE_PARAMETER 目標 (支援泛型參數註解 List<@NonNull String>)。module-info.java 中宣告 uses/provides 處理器。javax.annotation.processing 移出 JDK,需額外依賴 javax.annotation:javax.annotation-api。