Spring AOP實現原理
在之前的一文中介紹過Spring AOP的功能使用,但是沒有深究AOP的實現原理,今天正好看到幾篇好文,於是就自己整理了一下AOP實現的幾種方式,同時把代理模式相關知識也稍微整理一下。
代理模式
代理模式的UML類圖如下:
可以看到還是很簡單的,代理類實現了被代理類的接口,同時與被代理類是組合關系。下面看一下代理模式的實現。
靜態代理
接口類:
interface Person {
void speak();
}
真實實體類:
class Actor implements Person {
private String content;
public Actor(String content) {
this.content = content;
}
@Override
public void speak() {
System.out.println(this.content);
}
}
代理類:
class Agent implements Person {
private Actor actor;
private String before;
private String after;
public Agent(Actor actor, String before, String after) {
this.actor = actor;
this.before = before;
this.after = after;
}
@Override
public void speak() {
//before speak
System.out.println("Before actor speak, Agent say: " + before);
//real speak
this.actor.speak();
//after speak
System.out.println("After actor speak, Agent say: " + after);
}
}
測試方法:
public class StaticProxy {
public static void main(String[] args) {
Actor actor = new Actor("I am a famous actor!");
Agent agent = new Agent(actor, "Hello I am an agent.", "That's all!");
agent.speak();
}
}
結果:
動態代理
在講JDK的動態代理方法之前,不妨先想想如果讓你來實現一個可以任意類的任意方法的代理類,該怎么實現?有個很naive的做法,通過反射獲得Class和Method,再調用該方法,並且實現一些代理的方法。我嘗試了一下,很快就發現問題所在了。於是乎,還是使用JDK的動態代理接口吧。
JDK自帶方法
首先介紹一下最核心的一個接口和一個方法:
首先是java.lang.reflect包里的InvocationHandler接口:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
我們對於被代理的類的操作都會由該接口中的invoke方法實現,其中的參數的含義分別是:
- proxy:被代理的類的實例
- method:調用被代理的類的方法
- args:該方法需要的參數
使用方法首先是需要實現該接口,並且我們可以在invoke方法中調用被代理類的方法並獲得返回值,自然也可以在調用該方法的前后去做一些額外的事情,從而實現動態代理,下面的例子會詳細寫到。
另外一個很重要的靜態方法是java.lang.reflect包中的Proxy類的newProxyInstance方法:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
其中的參數含義如下:
- loader:被代理的類的類加載器
- interfaces:被代理類的接口數組
- invocationHandler:就是剛剛介紹的調用處理器類的對象實例
該方法會返回一個被修改過的類的實例,從而可以自由的調用該實例的方法。下面是一個實際例子。
Fruit接口:
public interface Fruit {
public void show();
}
Apple實現Fruit接口:
public class Apple implements Fruit{
@Override
public void show() {
System.out.println("<<<<show method is invoked");
}
}
代理類Agent.java:
public class DynamicAgent {
//實現InvocationHandler接口,並且可以初始化被代理類的對象
static class MyHandler implements InvocationHandler {
private Object proxy;
public MyHandler(Object proxy) {
this.proxy = proxy;
}
//自定義invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(">>>>before invoking");
//真正調用方法的地方
Object ret = method.invoke(this.proxy, args);
System.out.println(">>>>after invoking");
return ret;
}
}
//返回一個被修改過的對象
public static Object agent(Class interfaceClazz, Object proxy) {
return Proxy.newProxyInstance(interfaceClazz.getClassLoader(), new Class[]{interfaceClazz},
new MyHandler(proxy));
}
}
測試類:
public class ReflectTest {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
//注意一定要返回接口,不能返回實現類否則會報錯
Fruit fruit = (Fruit) DynamicAgent.agent(Fruit.class, new Apple());
fruit.show();
}
}
結果:
可以看到對於不同的實現類來說,可以用同一個動態代理類來進行代理,實現了“一次編寫到處代理”的效果。但是這種方法有個缺點,就是被代理的類一定要是實現了某個接口的,這很大程度限制了本方法的使用場景。下面還有另外一個使用了CGlib增強庫的方法。
CGLIB庫的方法
CGlib是一個字節碼增強庫,為AOP等提供了底層支持。下面看看它是怎么實現動態代理的。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGlibAgent implements MethodInterceptor {
private Object proxy;
public Object getInstance(Object proxy) {
this.proxy = proxy;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.proxy.getClass());
// 回調方法
enhancer.setCallback(this);
// 創建代理對象
return enhancer.create();
}
//回調方法
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println(">>>>before invoking");
//真正調用
Object ret = methodProxy.invokeSuper(o, objects);
System.out.println(">>>>after invoking");
return ret;
}
public static void main(String[] args) {
CGlibAgent cGlibAgent = new CGlibAgent();
Apple apple = (Apple) cGlibAgent.getInstance(new Apple());
apple.show();
}
}
結果:
可以看到結果和JDK動態代理是一樣的,但是可以直接對實現類進行操作而非接口,這樣會有很大的便利。
參考文獻
- 《Java編程思想》第14章
- java動態代理(JDK和cglib)