註解和反射是 Java 元編程的核心機制。Spring、Hibernate 等主流框架都大量使用這些特性。理解它們的原理和使用方式,有助於更好地理解框架設計和進行自定義擴展。

Java 註解#

內建註解#

Java 提供了幾個基礎註解:

註解作用
@Override標記方法覆寫父類方法
@Deprecated標記已過時的元素
@SuppressWarnings抑制編譯器警告
@FunctionalInterface標記函式式介面(Java 8)
public class AnnotationDemo {

    @Override
    public String toString() {
        return "demo";
    }

    @Deprecated
    public void oldMethod() {
        // 已過時的方法
    }

    @SuppressWarnings("unchecked")
    public void suppressWarning() {
        List list = new ArrayList();  // 原本會產生警告
    }
}

務必使用 @Override

不使用 @Override 時,如果方法簽名寫錯,編譯器會認為這是一個新方法而非覆寫,不會報錯。使用 @Override 可以讓編譯器幫你檢查。

元註解#

元註解是用來定義註解的註解:

元註解作用
@Target指定註解可以應用的目標(類、方法、字段等)
@Retention指定註解的保留策略
@Documented將註解包含在 Javadoc 中
@Inherited允許子類繼承父類的註解
@Repeatable允許同一位置重複使用(Java 8)

@Retention 保留策略#

public enum RetentionPolicy {
    SOURCE,   // 僅在源碼中保留,編譯後丟棄
    CLASS,    // 保留到 class 文件,執行時無法獲取(預設)
    RUNTIME   // 執行時可通過反射獲取
}

大多數框架使用的註解都是 RUNTIME 級別,因為需要在執行時通過反射讀取。

自定義註解#

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiEndpoint {
    String value();
    String method() default "GET";
    boolean auth() default true;
}

// 使用
@ApiEndpoint(value = "/users", method = "POST")
public class UserController {
    // ...
}
通過反射讀取註解
Class<?> clazz = UserController.class;

// 獲取類上的註解
if (clazz.isAnnotationPresent(ApiEndpoint.class)) {
    ApiEndpoint annotation = clazz.getAnnotation(ApiEndpoint.class);
    System.out.println("Path: " + annotation.value());
    System.out.println("Method: " + annotation.method());
    System.out.println("Auth: " + annotation.auth());
}

// 獲取方法上的註解
for (Method method : clazz.getDeclaredMethods()) {
    if (method.isAnnotationPresent(ApiEndpoint.class)) {
        ApiEndpoint annotation = method.getAnnotation(ApiEndpoint.class);
        // 處理
    }
}

反射機制#

什麼是反射?#

反射允許程式在執行時檢查和操作類、方法、字段等元素。這打破了 Java 的封裝性,但也提供了極大的靈活性。

獲取 Class 物件#

// 方式一:通過類名
Class<?> c1 = String.class;

// 方式二:通過實體
String str = "hello";
Class<?> c2 = str.getClass();

// 方式三:通過全限定名
Class<?> c3 = Class.forName("java.lang.String");

反射操作示例#

public class ReflectionDemo {
    private String name = "default";

    public void sayHello(String greeting) {
        System.out.println(greeting + ", " + name);
    }
}

// 反射操作
Class<?> clazz = ReflectionDemo.class;

// 創建實體
Object instance = clazz.getDeclaredConstructor().newInstance();

// 訪問私有字段
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);  // 突破私有限制
field.set(instance, "John");

// 呼叫方法
Method method = clazz.getMethod("sayHello", String.class);
method.invoke(instance, "Hello");  // 輸出: Hello, John

反射的效能開銷較大,不建議在熱點程式碼中頻繁使用。如果需要頻繁呼叫,可以快取 Method、Field 等物件。


動態代理#

JDK 動態代理#

JDK 動態代理只能代理介面

public interface UserService {
    void save(String name);
}

public class UserServiceImpl implements UserService {
    @Override
    public void save(String name) {
        System.out.println("Saving user: " + name);
    }
}

// 代理處理器
public class LoggingHandler implements InvocationHandler {
    private final Object target;

    public LoggingHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before: " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("After: " + method.getName());
        return result;
    }
}

// 創建代理
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new LoggingHandler(target)
);

proxy.save("Alice");
// 輸出:
// Before: save
// Saving user: Alice
// After: save

CGLib 動態代理#

CGLib 可以代理普通類,通過生成目標類的子類實現:

public class UserServiceImpl {
    public void save(String name) {
        System.out.println("Saving user: " + name);
    }
}

// CGLib 方法攔截器
public class LoggingInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args,
                           MethodProxy proxy) throws Throwable {
        System.out.println("Before: " + method.getName());
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After: " + method.getName());
        return result;
    }
}

// 創建代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
enhancer.setCallback(new LoggingInterceptor());
UserServiceImpl proxy = (UserServiceImpl) enhancer.create();

proxy.save("Bob");

JDK Proxy vs CGLib#

特性JDK ProxyCGLib
代理目標只能代理介面可代理普通類
實現方式實現介面生成子類
效能Java 8+ 最佳化後更快創建代理較慢,呼叫較快
final 類/方法不受影響無法代理
依賴JDK 內建需要額外依賴

選擇建議

  • 如果目標類有介面,優先使用 JDK Proxy(Spring AOP 預設行為)
  • 如果需要代理沒有介面的類,使用 CGLib
  • Spring Boot 2.x 預設使用 CGLib(即使有介面)
Spring AOP 的代理選擇

Spring AOP 會根據情況自動選擇代理方式:

// application.properties
# 強制使用 CGLibSpring Boot 2.x 預設
spring.aop.proxy-target-class=true

# 優先使用 JDK Proxy
spring.aop.proxy-target-class=false

判斷邏輯:

  1. 如果目標物件實現了介面且未強制 CGLib,使用 JDK Proxy
  2. 如果目標物件沒有介面,或強制使用 CGLib,則使用 CGLib

實際應用場景#

1. 框架開發#

Spring、MyBatis 等框架大量使用反射和動態代理:

// Spring 的 @Autowired 注入
// 通過反射獲取字段上的註解,然後注入依賴

// MyBatis 的 Mapper 介面
// 通過動態代理生成介面的實現類

2. ORM 框架#

// 簡化的 ORM 示例
public <T> T resultSetToObject(ResultSet rs, Class<T> clazz) throws Exception {
    T instance = clazz.getDeclaredConstructor().newInstance();
    for (Field field : clazz.getDeclaredFields()) {
        Column column = field.getAnnotation(Column.class);
        if (column != null) {
            field.setAccessible(true);
            field.set(instance, rs.getObject(column.value()));
        }
    }
    return instance;
}

3. 測試框架#

JUnit、Mockito 等測試框架使用反射呼叫測試方法:

// JUnit 通過反射找到 @Test 註解的方法並執行

實踐建議#

  1. 善用內建註解@Override@Deprecated 提高程式碼可讀性
  2. 自定義註解 + AOP:是實現橫切關注點的最佳方式
  3. 避免過度使用反射:影響效能和類型安全
  4. 快取反射物件:Method、Field 等物件可重複使用
  5. 注意 setAccessible 的安全風險:可能破壞封裝性
  6. 理解代理的限制:final 類/方法無法被 CGLib 代理