註解和反射是 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: saveCGLib 動態代理#
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 Proxy | CGLib |
|---|---|---|
| 代理目標 | 只能代理介面 | 可代理普通類 |
| 實現方式 | 實現介面 | 生成子類 |
| 效能 | Java 8+ 最佳化後更快 | 創建代理較慢,呼叫較快 |
| final 類/方法 | 不受影響 | 無法代理 |
| 依賴 | JDK 內建 | 需要額外依賴 |
選擇建議:
- 如果目標類有介面,優先使用 JDK Proxy(Spring AOP 預設行為)
- 如果需要代理沒有介面的類,使用 CGLib
- Spring Boot 2.x 預設使用 CGLib(即使有介面)
Spring AOP 的代理選擇
Spring AOP 會根據情況自動選擇代理方式:
// application.properties
# 強制使用 CGLib(Spring Boot 2.x 預設)
spring.aop.proxy-target-class=true
# 優先使用 JDK Proxy
spring.aop.proxy-target-class=false判斷邏輯:
- 如果目標物件實現了介面且未強制 CGLib,使用 JDK Proxy
- 如果目標物件沒有介面,或強制使用 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 註解的方法並執行實踐建議#
- 善用內建註解:
@Override、@Deprecated提高程式碼可讀性 - 自定義註解 + AOP:是實現橫切關注點的最佳方式
- 避免過度使用反射:影響效能和類型安全
- 快取反射物件:Method、Field 等物件可重複使用
- 注意 setAccessible 的安全風險:可能破壞封裝性
- 理解代理的限制:final 類/方法無法被 CGLib 代理