JAVA設計模式 - 代理模式

在對象的一個業務方法完成之後, 有時候我們可能需要去添加一些新的功能(前置校驗等). 但我們又不想更改原來的代碼 , 代理模式就爲我們提供了一種解決方案 .

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代理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章