掃二維碼與項(xiàng)目經(jīng)理溝通
我們在微信上24小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
本文轉(zhuǎn)載自微信公眾號「 JavaKeeper」,作者海星 。轉(zhuǎn)載本文請聯(lián)系 JavaKeeper公眾號。

基本介紹
代理模式是一種結(jié)構(gòu)型設(shè)計(jì)模式。為對象提供一個(gè)替身,以控制對這個(gè)對象的訪問。即通過代理對象訪問目標(biāo)對象,并允許在將請求提交給對象前后進(jìn)行一些處理。
被代理的對象可以是遠(yuǎn)程對象、創(chuàng)建開銷大的對象或需要安全控制的對象。
代理模式主要有三種不同的形式:
問題為什么要控制對于某個(gè)對象的訪問呢?舉個(gè)例子:有這樣一個(gè)消耗大量系統(tǒng)資源的巨型對象, 你只是偶爾需要使用它, 并非總是需要。
圖:refactoringguru.cn
你可以實(shí)現(xiàn)延遲初始化:在實(shí)際有需要時(shí)再創(chuàng)建該對象。對象的所有客戶端都要執(zhí)行延遲初始代碼。不幸的是, 這很可能會帶來很多重復(fù)代碼。
在理想情況下, 我們希望將代碼直接放入對象的類中, 但這并非總是能實(shí)現(xiàn):比如類可能是第三方封閉庫的一部分。
解決方案
代理模式建議新建一個(gè)與原服務(wù)對象接口相同的代理類, 然后更新應(yīng)用以將代理對象傳遞給所有原始對象客戶端。代理類接收到客戶端請求后會創(chuàng)建實(shí)際的服務(wù)對象, 并將所有工作委派給它。
圖:refactoringguru.cn
代理將自己偽裝成數(shù)據(jù)庫對象, 可在客戶端或?qū)嶋H數(shù)據(jù)庫對象不知情的情況下處理延遲初始化和緩存查詢結(jié)果的工作。
這有什么好處呢?如果需要在類的主要業(yè)務(wù)邏輯前后執(zhí)行一些工作, 你無需修改類就能完成這項(xiàng)工作。由于代理實(shí)現(xiàn)的接口與原類相同, 因此你可將其傳遞給任何一個(gè)使用實(shí)際服務(wù)對象的客戶端。
代理模式結(jié)構(gòu)
圖:refactoringguru.cn
打游戲有代練、買賣房子有中介代理、再比如一般公司投互聯(lián)網(wǎng)廣告也可以找代理公司,這里的代練、中介、廣告代理公司扮演的角色都是代理。
這里舉個(gè)更接近程序員的例子,比如有些變態(tài)的公司不允許在公司刷微博,看視頻,可以通過一層代理來限制我們訪問這些網(wǎng)站。
廢話不多說,先來個(gè)靜態(tài)代理。
靜態(tài)代理
1、定義網(wǎng)絡(luò)接口
- public interface Internet {
- void connectTo(String serverHost) throws Exception;
- }
2、真正的網(wǎng)絡(luò)連接
- public class RealInternet implements Internet{
- @Override
- public void connectTo(String serverHost) throws Exception {
- System.out.println("Connecting to "+ serverHost);
- }
- }
3、公司的網(wǎng)絡(luò)代理
- public class ProxyInternet implements Internet {
- //目標(biāo)對象,通過接口聚合
- private Internet internet;
- // 通過構(gòu)造方法傳入目標(biāo)對象
- public ProxyInternet(Internet internet){
- this.internet = internet;
- }
- //網(wǎng)絡(luò)黑名單
- private static List
bannedSites; - static
- {
- bannedSites = new ArrayList
(); - bannedSites.add("bilibili.com");
- bannedSites.add("youtube.com");
- bannedSites.add("weibo.com");
- bannedSites.add("qq.com");
- }
- @Override
- public void connectTo(String serverhost) throws Exception {
- // 添加限制功能
- if(bannedSites.contains(serverhost.toLowerCase()))
- {
- throw new Exception("Access Denied:"+serverhost);
- }
- internet.connectTo(serverhost);
- }
- }
4、客戶端驗(yàn)證
- public class Client {
- public static void main(String[] args) {
- Internet internet = new ProxyInternet(new RealInternet());
- try {
- internet.connectTo("so.com");
- internet.connectTo("qq.com");
- } catch (Exception e) {
- System.out.println(e.getMessage());
- }
- }
- }
5、輸出
- Connecting to so.com
- Access Denied:qq.com
不能訪問娛樂性網(wǎng)站,但是可以用 360 搜索,SO 靠譜,哈哈
靜態(tài)代理類優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
在不修改目標(biāo)對象的前提下,可以通過代理對象對目標(biāo)對象功能擴(kuò)展
代理使客戶端不需要知道實(shí)現(xiàn)類是什么,怎么做的,而客戶端只需知道代理即可(解耦合),對于如上的客戶端代碼,RealInterner() 可以應(yīng)用工廠將它隱藏。
缺點(diǎn):
代理類和委托類實(shí)現(xiàn)了相同的接口,代理類通過委托類實(shí)現(xiàn)了相同的方法。這樣就出現(xiàn)了大量的代碼重復(fù)。如果接口增加一個(gè)方法,除了所有實(shí)現(xiàn)類需要實(shí)現(xiàn)這個(gè)方法外,所有代理類也需要實(shí)現(xiàn)此方法。增加了代碼維護(hù)的復(fù)雜度。
代理對象只服務(wù)于一種類型的對象,如果要服務(wù)多類型的對象。勢必要為每一種對象都進(jìn)行代理,靜態(tài)代理在程序規(guī)模稍大時(shí)就無法勝任了。
動(dòng)態(tài)代理
靜態(tài)代理會產(chǎn)生很多靜態(tài)類,所以我們要想辦法可以通過一個(gè)代理類完成全部的代理功能,這就引出了動(dòng)態(tài)代理。
JDK原生動(dòng)態(tài)代理
在 Java 中要想實(shí)現(xiàn)動(dòng)態(tài)代理機(jī)制,需要 java.lang.reflect.InvocationHandler 接口和 java.lang.reflect.Proxy 類的支持
Coding
1、網(wǎng)絡(luò)接口不變
- public interface Internet {
- void connectTo(String serverHost) throws Exception;
- }
2、真正的網(wǎng)絡(luò)連接,也不會改變
- public class RealInternet implements Internet{
- @Override
- public void connectTo(String serverHost) throws Exception {
- System.out.println("Connecting to "+ serverHost);
- }
- }
3、動(dòng)態(tài)代理,需要實(shí)現(xiàn) InvocationHandler,我們用 Lambda 表達(dá)式簡化下
- public class ProxyFactory {
- /**
- * 維護(hù)一個(gè)目標(biāo)對象
- **/
- private Object target;
- /**
- * 構(gòu)造器,初始化目標(biāo)對象
- **/
- public ProxyFactory(Object target) {
- this.target = target;
- }
- public Object getProxyInstance() {
- /**
- 被代理對象target通過參數(shù)傳遞進(jìn)來,
- 通過target.getClass().getClassLoader()獲取ClassLoader對象,
- 然后通過target.getClass().getInterfaces()獲取它實(shí)現(xiàn)的所有接口,
- 再將target包裝到實(shí)現(xiàn)了InvocationHandler接口的對象中。
- 通過newProxyInstance函數(shù)我們就獲得了一個(gè)動(dòng)態(tài)代理對象。
- */
- return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- if(bannedSites.contains(args[0].toString().toLowerCase()))
- {
- throw new Exception("Access Denied:"+args[0]);
- }
- //反射機(jī)制調(diào)用目標(biāo)對象的方法
- Object obj = method.invoke(target, args);
- return obj;
- }
- });
- }
- private static List
bannedSites; - static
- {
- bannedSites = new ArrayList
(); - bannedSites.add("bilibili.com");
- bannedSites.add("youtube.com");
- bannedSites.add("weibo.com");
- bannedSites.add("qq.com");
- }
- }
4、客戶端
- public class Client {
- public static void main(String[] args) {
- Internet internet = new ProxyInternet(new RealInternet());
- try {
- internet.connectTo("#");
- internet.connectTo("qq.com");
- } catch (Exception e) {
- System.out.println(e.getMessage());
- }
- }
- }
動(dòng)態(tài)代理的方式中,所有的函數(shù)調(diào)用最終都會經(jīng)過 invoke 函數(shù)的轉(zhuǎn)發(fā),因此我們就可以在這里做一些自己想做的操作,比如日志系統(tǒng)、事務(wù)、攔截器、權(quán)限控制等。
cglib代理
靜態(tài)代理和 JDK 代理模式都要求目標(biāo)對象實(shí)現(xiàn)一個(gè)接口,但有時(shí)候目標(biāo)對象只是一個(gè)單獨(dú)的對象,并沒有實(shí)現(xiàn)任何接口,這個(gè)時(shí)候就可以使用目標(biāo)對象子類來實(shí)現(xiàn)代理,這就是 cglib 代理。
Coding
添加 cglib 依賴
cglib cglib 3.3.0
1、不需要接口
- public class RealInternet{
- public void connectTo(String serverHost) {
- System.out.println("Connecting to "+ serverHost);
- }
- }
2、代理工廠類
- public class ProxyFactory implements MethodInterceptor {
- private Object target;
- public ProxyFactory(Object target){
- this.target = target;
- }
- @Override
- public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
- System.out.println("cglib 代理開始,可以添加邏輯");
- Object obj = method.invoke(target,objects);
- System.out.println("cglib 代理結(jié)束");
- return obj;
- }
- public Object getProxyInstance(){
- //工具類,類似于JDK動(dòng)態(tài)代理的Proxy類
- Enhancer enhancer = new Enhancer();
- //設(shè)置父類
- enhancer.setSuperclass(target.getClass());
- //設(shè)置回調(diào)函數(shù)
- enhancer.setCallback(this);
- //創(chuàng)建子類對象,即代理對象
- return enhancer.create();
- }
- }
3、客戶端
- public class Client {
- public static void main(String[] args) {
- //目標(biāo)對象
- RealInternet target = new RealInternet();
- //獲取代理對象,并且將目標(biāo)對象傳遞給代理對象
- RealInternet internet = (RealInternet) new ProxyFactory(target).getProxyInstance();
- internet.connectTo("so.cn");
- }
- }
4、輸出
- cglib 代理開始,可以添加邏輯
- Connecting to so.cn
- cglib 代理結(jié)束
代理模式適合應(yīng)用場景
使用代理模式的方式多種多樣, 我們來看看最常見的幾種。
代理可僅在客戶端憑據(jù)滿足要求時(shí)將請求傳遞給服務(wù)對象。
代理會將所有獲取了指向服務(wù)對象或其結(jié)果的客戶端記錄在案。代理會時(shí)不時(shí)地遍歷各個(gè)客戶端, 檢查它們是否仍在運(yùn)行。如果相應(yīng)的客戶端列表為空, 代理就會銷毀該服務(wù)對象, 釋放底層系統(tǒng)資源。
代理還可以記錄客戶端是否修改了服務(wù)對象。其他客戶端還可以復(fù)用未修改的對象。
AOP 中的代理模式
AOP(面向切面編程)主要的的實(shí)現(xiàn)技術(shù)主要有 Spring AOP 和 AspectJ
AspectJ 的底層技術(shù)就是靜態(tài)代理,用一種 AspectJ 支持的特定語言編寫切面,通過一個(gè)命令來編譯,生成一個(gè)新的代理類,該代理類增強(qiáng)了業(yè)務(wù)類,這是在編譯時(shí)增強(qiáng),相對于下面說的運(yùn)行時(shí)增強(qiáng),編譯時(shí)增強(qiáng)的性能更好。(AspectJ 的靜態(tài)代理,不像我們前邊介紹的需要為每一個(gè)目標(biāo)類手動(dòng)編寫一個(gè)代理類,AspectJ 框架可以在編譯時(shí)就生成目標(biāo)類的“代理類”,在這里加了個(gè)冒號,是因?yàn)閷?shí)際上它并沒有生成一個(gè)新的類,而是把代理邏輯直接編譯到目標(biāo)類里面了)
Spring AOP 采用的是動(dòng)態(tài)代理,在運(yùn)行期間對業(yè)務(wù)方法進(jìn)行增強(qiáng),所以不會生成新類,對于動(dòng)態(tài)代理技術(shù),Spring AOP 提供了對 JDK 動(dòng)態(tài)代理的支持以及 CGLib 的支持。
默認(rèn)情況下,Spring對實(shí)現(xiàn)了接口的類使用 JDK Proxy方式,否則的話使用CGLib。不過可以通過配置指定 Spring AOP 都通過 CGLib 來生成代理類。
具體邏輯在 org.springframework.aop.framework.DefaultAopProxyFactory類中,使用哪種方式生成由AopProxy 根據(jù) AdvisedSupport 對象的配置來決定源碼如下:
- public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
- public DefaultAopProxyFactory() {
- }
- public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
- if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
- return new JdkDynamicAopProxy(config);
- } else {
- Class> targetClass = config.getTargetClass();
- if (targetClass == null) {
- throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
- } else {
- //如果目標(biāo)類是接口且是代理類, 使用JDK動(dòng)態(tài)代理類,否則使用Cglib生成代理類
- return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
- }
- }
- }
- private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
- }
- }
具體內(nèi)容就不展開了,后邊整理 SpringAOP 的時(shí)候再深入。
參考與感謝https://refactoringguru.cn/design-patterns/proxy https://www.geeksforgeeks.org/proxy-design-pattern/

我們在微信上24小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流