AOP(面向切面編程)的核心在於將橫切關注點(如日誌、事務、權限)與業務邏輯分離。根據織入時機的不同,AOP 實現主要分為靜態 AOP與動態 AOP兩大類。靜態 AOP 在編譯期直接修改字節碼;動態 AOP 則在運行期通過代理、字節碼生成或類加載器介入實現織入。下表對主流機制進行對比:
| 類別 | 機制 | 原理 | 優點 | 缺點 |
|---|---|---|---|---|
| 靜態 AOP | 靜態織入 | 編譯期將切面以字節碼形式編譯進目標類 | 運行期零性能損耗 | 靈活性不足,需專用編譯器 |
| 動態 AOP | 動態代理 (JDK Proxy) | 運行期為接口動態生成代理類,植入切面邏輯 | 不修改原始碼,靈活性高 | 目標類必須實現接口;反射調用有性能開銷 |
| 動態字節碼生成 (CGLIB) | 運行期繼承目標類生成子類,重寫方法植入切面 | 無需接口,可代理普通類 | 無法代理 final 方法;生成類佔用元空間 | |
| 自定義類加載器 | 類加載前修改字節碼,植入切面邏輯 | 可織入大多數類,性能優於代理 | 其他類加載器加載的類無法攔截 | |
| 字節碼轉換 | 基於 Instrumentation,所有類加載前統一攔截轉換 | 全局攔截,無類加載器限制 | 配置較繁瑣,需 -javaagent 啟動參數 |
織入器根據 Pointcut 搜索目標類的 Joinpoint,將 Advice 織入生成代理類。關係圖示:

JDK 動態代理需四大角色:目標類、目標接口、織入器、InvocationHandler。以下示例演示在方法前織入日誌:
public static void main(String[] args) {
Class[] proxyInterface = { IBusiness.class, IBusiness2.class };
LogInvocationHandler handler = new LogInvocationHandler(new Business());
ClassLoader classLoader = DynamicProxyDemo.class.getClassLoader();
IBusiness2 proxyBusiness = (IBusiness2) Proxy.newProxyInstance(classLoader, proxyInterface, handler);
proxyBusiness.doSomeThing2();
((IBusiness) proxyBusiness).doSomeThing();
}
public static class LogInvocationHandler implements InvocationHandler {
private Object target;
public LogInvocationHandler(Object target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object rev = method.invoke(target, args); // 執行原邏輯
if (method.getName().equals("doSomeThing2")) {
System.out.println("記錄日誌"); // 織入邏輯
}
return rev;
}
}
業務類實現兩個接口,輸出顯示日誌成功切入 doSomeThing2 方法前。
核心入口 Proxy.newProxyInstance 僅三步:
Class cl = getProxyClass(loader, interfaces)InvocationHandler 參數的構造器getProxyClass 先查緩存,無則調用 ProxyGenerator.generateProxyClass 生成字節碼,再用 defineClass0 載入 JVM。生成的代理類實現所有目標接口,持有 InvocationHandler 引用,所有方法體委派給 handler.invoke()。
public final class $Proxy0 extends Proxy implements IBusiness, IBusiness2 {
protected $Proxy0(InvocationHandler h) { super(h); }
public void doSomeThing2() {
try { h.invoke(this, m3, null); } catch (Throwable e) { ... }
}
// 其他方法同理
}
-XX:MaxMetaspaceSizeCGLIB 通過繼承目標類生成子類,攔截父類方法調用,無需接口。核心類 Enhancer,回調接口 MethodInterceptor。
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Business.class);
enhancer.setCallback(new LogIntercept());
IBusiness2 newBusiness = (IBusiness2) enhancer.create();
newBusiness.doSomeThing2();
public static class LogIntercept implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object rev = proxy.invokeSuper(obj, args); // 調用父類方法
if (method.getName().equals("doSomeThing2")) {
System.out.println("記錄日誌");
}
return rev;
}
}
注意:final 方法無法被重寫,故不可攔截。
原理:自定義 ClassLoader,在 defineClass 前用 Javassist 修改字節碼。Javassist 提供 CtClass、CtMethod 等 API,操作極簡單。
ClassPool cp = ClassPool.getDefault();
Loader cl = new Loader();
cl.addTranslator(cp, new MyTranslator());
cl.run("demo.JavassistAopDemo$MyTranslator", args);
public static class MyTranslator implements Translator {
public void onLoad(ClassPool pool, String classname) {
if (!"model$Business".equals(classname)) return;
try {
CtClass cc = pool.get(classname);
CtMethod m = cc.getDeclaredMethod("doSomeThing");
m.insertBefore("{ System.out.println(\"記錄日誌\"); }");
} catch (Exception e) { e.printStackTrace(); }
}
}
優點:無新類生成,性能最優;缺點:無法攔截非該加載器載入的類。
基於 Java 5 java.lang.instrument.Instrumentation,實現 ClassFileTransformer,在所有類加載前統一攔截。配合 premain 方法與 -javaagent 參數實現零侵入 AOP。
public class MyClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
if (!"model/Business".equals(className)) return null;
className = className.replace('/', '.');
try {
CtClass cc = ClassPool.getDefault().get(className);
CtMethod m = cc.getDeclaredMethod("doSomeThing");
m.insertBefore("{ System.out.println(\"記錄日誌\"); }");
return cc.toBytecode();
} catch (Exception e) { return null; }
}
public static void premain(String args, Instrumentation ins) {
ins.addTransformer(new MyClassFileTransformer());
}
}
打包為 JAR,MANIFEST.MF 指定 Premain-Class,啟動添加 -javaagent:path/to/agent.jar。可攔截系統類加載器載入的類,實現真正的全局 AOP。
AopContext.currentProxy() 獲取代理對象)// 正確:通過代理調用,觸發切面
public boolean evaluateMsg() {
return ((IMsgFilterService) AopContext.currentProxy()).evaluateMsg("msg");
}
// 錯誤:直接調用,繞過代理
public boolean evaluateMsg() {
return evaluateMsg("msg"); // 不會織入切面
}