反射是 Java 動態性的基石,框架開發、註解驅動、熱加載、動態代理等高級特性均建立於此。本文從「是什麼、為什麼、怎麼用、性能代價」四個維度,系統講解反射機制的核心原理與工程實踐。
反射是程序在運行時檢查自身結構、動態加載類、創建實例、訪問成員、調用方法的能力。打破了編譯期靜態綁定,實現了動態編譯/綁定。
| 綁定時機 | 靜態編譯 | 動態編譯(反射) |
|---|---|---|
| 類型確定 | 編譯期 | 運行時 |
| 對象創建 | new ClassName() | Class.forName().newInstance() |
| 方法調用 | obj.method() | method.invoke(obj) |
| 耦合度 | 高(硬編碼依賴) | 低(配置/字符串驅動) |
| 性能 | 極高(直接調用/JIT 內聯) | 較低(訪問檢查/參數打包/解包) |
// 傳統工廠:新增產品需修改工廠代碼(違反開閉原則)
class Factory {
public static Fruit getInstance(String name) {
if ("Apple".equals(name)) return new Apple();
if ("Orange".equals(name)) return new Orange();
return null;
}
}
// 反射工廠:新增產品只需配置,無需修改代碼
class ReflectiveFactory {
public static Fruit getInstance(String className) {
try {
return (Fruit) Class.forName(className).getDeclaredConstructor().newInstance();
} catch (Exception e) { throw new RuntimeException(e); }
}
}
// 配置文件: fruit.impl=com.fruit.Apple
Class.forName("com.mysql.jdbc.Driver")@Component、@RequestMapping@Test 方法Field field = TargetClass.class.getDeclaredField("privateField");
field.setAccessible(true); // 關鍵:繞過訪問檢查
Object value = field.get(targetInstance);
field.set(targetInstance, newValue);
警告:生產代碼極少應用此技巧,破壞封裝邊界,導致維護災難。僅用於測試、框架內部、緊急熱修復。
// 1. 已知類字面量
Class<User> clazz1 = User.class;
// 2. 已知實例
User user = new User();
Class<?> clazz2 = user.getClass();
// 3. 字符串全限定名 (最常用於動態加載)
Class<?> clazz3 = Class.forName("com.example.User");
| 操作 | 核心 API | 關鍵方法 |
|---|---|---|
| 創建實例 | Constructor | getConstructor(), newInstance() |
| 調用方法 | Method | getMethod(), invoke(obj, args...) |
| 訪問字段 | Field | getField(), get(obj), set(obj, val) |
| 獲取註解 | AnnotatedElement | getAnnotation(), getAnnotations() |
| 泛型類型 | Type / ParameterizedType | getGenericSuperclass(), getActualTypeArguments() |
public class ReflectionInvoker {
public static Object invoke(String className, String methodName, Object... args) {
try {
Class<?> clazz = Class.forName(className);
Object instance = clazz.getDeclaredConstructor().newInstance();
// 參數類型數組
Class<?>[] paramTypes = Arrays.stream(args)
.map(Object::getClass)
.toArray(Class[]::new);
Method method = clazz.getDeclaredMethod(methodName, paramTypes);
method.setAccessible(true); // 支持私有方法
return method.invoke(instance, args);
} catch (Exception e) {
throw new RuntimeException("反射調用失敗: " + className + "#" + methodName, e);
}
}
}
setAccessible(true) 觸發 SecurityManager checkPermission| 操作 | 相對耗時 | 備註 |
|---|---|---|
| 直接方法調用 | 1x (基準) | JIT 內聯後極快 |
| Method.invoke (public) | 5-10x | 首次調用慢,後續 JIT 優化 |
| Method.invoke (setAccessible) | 10-20x | 繞過訪問檢查但仍有打包開銷 |
| Field.get/set | 3-8x | 類似方法調用 |
| Constructor.newInstance | 50-100x | 對象創建+初始化開銷大 |
Method、Field、Constructor 線程安全可復用,嚴禁在熱路徑重複獲取setAccessible(true) 也無法訪問module-info.java 中 opens com.pkg to reflecting.module--add-opens java.base/java.lang=ALL-UNNAMED (臨時突破)