動態代理雙劍客--JDK Proxy與CGLIB
背景:
研究過設計模式的同胞們都知道代理模式可以有兩種實現方案:
1.介面實現(或繼承抽象類)
核心程式碼片段
ProxySubject-->>doOperation()
//dosomething before
realSubject.doOperation()
//dosomething after
2.繼承父類
核心程式碼片段
ProxySubject-->>doOperation()
//dosomething before
super.doOperation()
//dosomething after
總結:
相同點
都可以通過Proxy控制對Target
不同點
可行性
如果Target實現了介面,那麼這兩種方式都可以;
如果沒有實現任何介面,那隻能採取“繼承父類”的方式了
正文
Java中動態代理對應著也有兩種實現方式
1.“介面實現"---JDK Proxy
用到JDK提供的InvocationHandler介面和Proxy類
類之間的關係如下
InvocationHandler介面
用於處理方法請求
Proxy類
用於生成代理物件
程式碼演示
ISubject介面
public interface ISubject {
public void showName(String name);
}
RealSubject
public class RealSubject implements ISubject {
@Override
public void showName(String name) {
System.out.println(name+"閃亮登場");
}
}
LogHandler類
為了更明確的說明動態代理的工作原理,將代理的建立過程放到了LogHandler的外部,即main方法中
客戶端類Clientpublic class LogHandler implements InvocationHandler { Object target=null; public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result=null; //呼叫目標物件方法前的邏輯 System.out.println("下面有一個大人物要出現"); //呼叫目標物件的方法,這句程式碼將代理與目標類聯絡了起來 method.invoke(target, args); //呼叫目標物件方法後的邏輯 System.out.println("大家鼓掌歡迎"); return result; } }
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
LogHandler logHandler=new LogHandler();
logHandler.setTarget(new RealSubject());
//建立代理物件
ISubject proxySubject=(ISubject)Proxy.newProxyInstance(RealSubject.class.getClassLoader(), RealSubject.class.getInterfaces(), logHandler);
System.out.println("-------JDK Proxy-------------");
proxySubject.showName("委座");
}
}
執行結果
呼叫過程時序圖
2.“繼承父類”---CGLIB
用到了CBLIB提供的Enhancer類和MethodInterceptor介面
類之間的關係如下
需要引入第三方jar包
- cglib-2.2.jar
- asm-3.1.jar
Enhancer類
用於建立代理物件
MethodInterceptor介面
用於處理方法請求
程式碼演示
RealSubject類同上,但是個pojo類
public class RealSubject {
public void showName(String name) {
System.out.println(name+"閃亮登場");
}
}
LogIntercept類
public class LogIntercept implements MethodInterceptor {
Object target=null;
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2,
MethodProxy arg3) throws Throwable {
Object result=null;
//呼叫目標物件方法前的邏輯
System.out.println("下面有一個大人物要出現");
//呼叫目標物件的方法,這句程式碼將代理與目標類聯絡了起來
arg3.invoke(target, arg2);
//呼叫目標物件方法後的邏輯
System.out.println("大家鼓掌歡迎");
return result;
}
}
客戶端類Client
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
LogIntercept logIntercept=new LogIntercept();
logIntercept.setTarget(new RealSubject());
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(RealSubject.class);
enhancer.setCallback(logIntercept);
<pre name="code" class="java"> RealSubject proxySubject=(RealSubject )enhancer.create();
System.out.println("-------CBLIB-------------");
proxySubject.showName("委座");
}
}
呼叫過程時序圖
總結
大家可以看到JDK Proxy和CGLIB這兩種動態代理的實現過程是非常相似的,但也有區別
相同點:
- 都用到了一個介面一個類;
- 介面用於處理方法呼叫,類用於建立代理物件
JDK Proxy
InvocationHandler介面
Proxy類
CGLIB
MethodIntercept介面
Enhancer類
不同點:
JDK Proxy
使用目標類的介面建立動態代理
CBLIB
使用目標類的子類建立動態代理
最後
JDK Proxy和CGLIB兩種動態代理各有千秋,具體用哪個方案要看具體情況。如果目標類實現了對應介面,兩種方案都可以;如果沒有實現任何介面則要使用CBLIB。比如Hibernate中的實體類是POJO類,沒有實現任何介面,那麼要通過代理實現延遲載入就只能採用CGLIB方案了。