反射與動態代理是 Java 進階開發的兩大基石,前者賦予程序「自省」能力,後者實現了關注點分離。本文將從類加載機制切入,層層遞進至動態代理實現,最後結合 AOP 展示企業級應用場景。
反射的本質是:在運行時獲取類的完整結構信息(字段、方法、構造器等),並能動態創建實例、調用方法。其核心入口是 java.lang.Class 對象,代表運行時類元數據。
Class.forName("全限定類名") - 最常用,支持動態加載類名.class - 編譯期已知類型時使用對象.getClass() - 從實例反推類信息// 獲取運行時類
Class<StudentExam> clazz = StudentExam.class;
// 實例化(需無參構造器)
StudentExam studentExam = clazz.newInstance();
// 操作字段(含私有)
Field field = clazz.getDeclaredField("id");
field.setAccessible(true); // 關鍵:暴力反射破解私有封裝
field.set(studentExam, 11);
// 調用方法
Method method = clazz.getMethod("setId", Integer.class);
method.invoke(studentExam, 123);
// 獲取所有聲明方法(含私有,不含繼承)
Method[] methods = clazz.getDeclaredMethods();
類加載器負責將 .class 文件加載進 JVM 內存。利用它可讀取類路徑下的配置文件:
public void classLoader() throws IOException {
ClassLoader loader = this.getClass().getClassLoader();
// 路徑從 classpath 根目錄開始,用 / 分隔
InputStream inStream = loader.getResourceAsStream("config.properties");
Properties properties = new Properties();
properties.load(inStream);
System.out.println(properties.getProperty("domain"));
}
代理類與目標類在編譯期確定,一個代理只能服務一個接口,導致類爆炸,擴展性極差。
public class ProxyTest {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
MyInvocationHandler handler = new MyInvocationHandler();
Subject subject = (Subject) handler.bind(realSubject);
subject.action(); // 實際調用 handler.invoke()
}
}
interface Subject { void action(); }
class RealSubject implements Subject {
@Override public void action() { System.out.println("真實業務邏輯執行..."); }
}
class MyInvocationHandler implements InvocationHandler {
private Object target;
public Object bind(Object target) {
this.target = target;
// 關鍵:類加載器、實現接口、處理器
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("【前置增強】方法調用前...");
Object result = method.invoke(target, args); // 調用真實方法
System.out.println("【後置增強】方法調用後...");
return result;
}
}
AOP(面向切面編程)本質上就是動態代理的規範化應用。Spring AOP 底層正是基於 JDK 動態代理(接口場景)或 CGLIB(類場景)實現。
public class ArithmeticCalculatorLoggingProxy {
private ArithmeticCalculator target;
public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
this.target = target;
}
public ArithmeticCalculator getLoggingProxy() {
return (ArithmeticCalculator) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{ArithmeticCalculator.class},
(proxy, method, args) -> {
String methodName = method.getName();
System.out.println("[Before] " + methodName + " args: " + Arrays.asList(args));
Object result = null;
try {
result = method.invoke(target, args); // 目標方法執行
System.out.println("[After Returning] result: " + result);
} catch (Exception e) {
System.out.println("[After Throwing] exception: " + e.getMessage());
throw e;
} finally {
System.out.println("[After] 方法結束");
}
return result;
}
);
}
}
public class Main {
public static void main(String[] args) {
ArithmeticCalculator calculator = new ArithmeticCalculatorImpl();
// 包裝代理
calculator = new ArithmeticCalculatorLoggingProxy(calculator).getLoggingProxy();
calculator.add(11, 12); // 自動觸發日誌切面
calculator.div(21, 3);
}
}
輸出將包含完整的方法進入、參數、返回值、畂常、退出日誌,且業務代碼零侵入。這正是 Spring @AspectJ 註解驅動的底層邏輯縮影。