在對象的一個業務方法完成之後, 有時候我們可能需要去添加一些新的功能(前置校驗等). 但我們又不想更改原來的代碼 , 代理模式就爲我們提供了一種解決方案 .
1 . 代理模式的定義
代理模式就是在不改變原有代碼的基礎上 , 實現對目標對象的功能擴展 .
以現實的情況爲例 , 目前有一個藝人, 她會表演和唱歌 . 現在想擴展一個行爲來爲這個藝人來接通告 , 通常情況下我們是不想把這個業務交給原來的藝人去做的, 畢竟也不是她的職責 . 這時候我們就可以引入一個代理對象 , 即經紀人 . 經紀人去來完成收發通告的一些擴展業務 , 但需要表演時 , 仍然要經紀人(代理對象)通知藝人(目標對象)來完成功能 .
代理模式分爲靜態代理 , 動態代理(根據實現方式又分爲JDK動態代理和CGLIB動態代理) .
2 . 代理模式的特點
不影響原有業務代碼實現新功能擴展
3 . 代理模式的主要角色
- 抽象接口 : 即目標對象實現的接口 , 在靜態代理和JDK代理中是必備角色
- 目標對象 : 就是實現了具體業務的類 , 在代理模式下仍然需要目標對象來完成原本業務
- 代理對象 : 目標對象的代理類 , 對目標對象的原有業務增加擴展操作
4 . 代理模式的應用場景
最經典的就是Spring中的AOP , 它的原理就是通過動態代理來完成
5 . 代碼實現
5.1 靜態代理
靜態代理就是通過實現與目標相同的接口 , 並注入原本的代理目標對象 . 在重寫的方法中完成擴展 , 並調用目標對象來實現原有操作
package com.xbz.xstudy.shejimoshi.proxy.targetObject;
/**
* @title 需要代理的目標接口(藝人)
* @author Xingbz
* @createDate 2019-4-23
*/
public interface ActorInterface {
void show();
}
package com.xbz.xstudy.shejimoshi.proxy.targetObject;
/**
* @title 需要代理的目標實現對象(藝人)
* @author Xingbz
* @createDate 2019-4-23
*/
public class ActorInterfaceImpl implements ActorInterface {
@Override
public void show() {
System.out.println("藝人完成了一場表演 ~ ");
}
}
package com.xbz.xstudy.shejimoshi.proxy.staticState;
import com.xbz.xstudy.shejimoshi.proxy.targetObject.ActorInterface;
/**
* @title 靜態代理對象(經紀人)
* @author Xingbz
* @createDate 2019-4-23
*/
public class ActorProxy implements ActorInterface {
private ActorInterface actor;
public ActorProxy(ActorInterface actor) {
this.actor = actor;
}
@Override
public void show() {
System.out.println("[靜態代理]經紀人爲藝人接了通告");
actor.show();//通知藝人自身來完成行爲
System.out.println("[靜態代理]經紀人爲藝人完成通告");
}
}
package com.xbz.xstudy.shejimoshi.proxy.staticState;
import com.xbz.xstudy.shejimoshi.proxy.targetObject.ActorInterface;
import com.xbz.xstudy.shejimoshi.proxy.targetObject.ActorInterfaceImpl;
/**
* @title 靜態代理測試類
* @author Xingbz
* @createDate 2019-4-22
*/
public class Demo {
public static void main(String[] args){
ActorInterface actor = new ActorInterfaceImpl();
actor.show();//正常執行方法
ActorInterface actorProxy = new ActorProxy(actor);
actorProxy.show();//實際還是actor自身的操作, 但在方法執行前後會有一些經紀人的代理操作
}
}
靜態代理的優點 : 可以做到不對目標對象進行任何修改的前提下 , 將目標對象實現功能擴展
靜態代理的缺點 : 因爲代理對象需要實現與目標對象一樣的接口 , 會導致代理類非常繁多 , 不易維護 . 並且如果接口增加方法 , 目標對象和代理對象都需要變動
5.2 JDK動態代理
JDK動態代理就是指動態的在內存中創建代理對象 . 不用實現任何藉口 , 通過傳入的目標對象和指定接口 , 利用反射調用JDK API動態的創建代理類
package com.xbz.xstudy.shejimoshi.proxy.dynamic.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @title 動態代理Handler
* @author Xingbz
* @createDate 2019-4-23
*/
public class ProxyHandler implements InvocationHandler {
private Object targetObject;//用於接收代理目標實際對象
public ProxyHandler(Object targetObject) {
this.targetObject = targetObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[JDK動態代理]經紀人爲藝人接了通告");
method.invoke(targetObject, args);//通知藝人自身來完成行爲
System.out.println("[JDK動態代理]經紀人爲藝人完成通告");
return proxy;
}
}
package com.xbz.xstudy.shejimoshi.proxy.dynamic.jdk;
import com.xbz.xstudy.shejimoshi.proxy.ProxyClassOutUtil;
import com.xbz.xstudy.shejimoshi.proxy.targetObject.ActorInterface;
import com.xbz.xstudy.shejimoshi.proxy.targetObject.ActorInterfaceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* @title JDK動態代理測試類
* @author Xingbz
* @createDate 2019-4-23
*/
public class Demo {
public static void main(String[] args){
ActorInterface actor = new ActorInterfaceImpl();
InvocationHandler actorHandler= new ProxyHandler(actor);
ActorInterface actorProxy = (ActorInterface) Proxy.newProxyInstance(ActorInterface.class.getClassLoader(), new Class[]{ActorInterface.class}, actorHandler);
actorProxy.show();
}
}
JDK動態代理的優點 : 無需實現接口 , 免去編寫很多代理類的繁瑣
JDK動態代理的缺點 : 代理對象不需實現接口 , 但目標對象必須實現接口 , 如果目標對象沒有接口的話就不能使用JDK動態代理了
5.3 CGLIB動態代理
cglib是通過掃描該類及其父類中的所有public方法 , 通過asm動態生成該類的子類字節碼 . 在子類中重寫了該類的所有方法 , 加入擴展邏輯 , 然後返回該子類的實例作爲代理類 . 即是說 , cglib是通過該類的子類作爲代理類來實現代理操作的 , 所以代理目標中的非public方法及static/final修飾的方法 , 不能實現代理 .
asm是一個小而快的字節碼處理框架 , 它負責生成從代理目標類中掃描出的方法字節碼 , 並將這些字節碼暫存在內存中 . 然後我們通過某種方式將這些字節碼轉換爲class,最勇利用反射創建代理類的實例返回 .
使用該方法需要引入cglib和asm依賴 , spring已經內嵌了這兩個jar包 . 如果項目中有用到spring則無需再引入 .
package com.xbz.xstudy.shejimoshi.proxy.targetObject;
/**
* @title 未實現接口的代理目標對象(主播)
* @author Xingbz
* @createDate 2019-4-23
*/
public class Anchor {
public void sing(String song) {
System.out.println("主播唱了一首歌 : " + song);
}
}
package com.xbz.xstudy.shejimoshi.proxy.dynamic.cglib;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @title CGLIB動態代理Inerceptor
* @description
* @author Xingbz
* @createDate 2019-4-23
*/
public class MyMethodInerceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("[CGLIB動態代理]助理爲主播選了一首歌");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("[CGLIB動態代理]助理帶頭爲主播刷禮物");
return result;
}
}
package com.xbz.xstudy.shejimoshi.proxy.dynamic.cglib;
import com.xbz.xstudy.shejimoshi.proxy.ProxyClassOutUtil;
import com.xbz.xstudy.shejimoshi.proxy.targetObject.Anchor;
import org.springframework.cglib.proxy.Enhancer;
/**
* @title CGLIB動態代理測試類
* @author Xingbz
* @createDate 2019-4-23
*/
public class Demo {
public static void main(String[] args){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Anchor.class);
enhancer.setCallback(new MyMethodInerceptor());
Anchor anchor = (Anchor) enhancer.create();
anchor.sing("今天是個好日子");
}
}
CGLIB動態代理的優點 : 無需目標對象實現接口
CGLIB動態代理的缺點 : 非public , 或statsi/final修飾的方法無法被代理
6 . 總結
三種代理各有優缺點和適用範圍 , 主要看目標是否實現了接口 . 在Spring框架的AOP中 , 如果納入容器的bean有實現接口 , 則用JDK代理 . 如果沒有實現接口 , 則用CGLIB代理