天天看點

Java設計模式之代理模式Java設計模式之代理模式

Java設計模式之代理模式

代理模式屬于結構型設計模式,代理模式是為一個類提供一個替身,以控制對這個類的通路,通過代理類通路被代理類。

代理模式的三種形式

(1):靜态代理

(2):動态代理(JDK代理、接口代理)

(3):Cglib代理(動态代理,可以在記憶體中動态的建立對象)

實作靜态代理UML類圖

Java設計模式之代理模式Java設計模式之代理模式

實作動态代理UML類圖

Java設計模式之代理模式Java設計模式之代理模式

一、 建立靜态代理的步驟

(1):建立一個operator 接口和實作operator 接口的實體類ChinaTelecom

(2):建立一個代理類PhoneProxy 并實作接口

(1)建立一個operator 接口和實作operator 接口的實體類ChinaTelecom

/**
 * @author yly
 * @ClassName ChinaTelecom
 * @Date 2020/2/20 19:28
 * @Version 1.0
 **/
public interface operator {
   public void accessToTheNetwork();
}
           
/**
 * @author yly
 * @ClassName ChinaTelecom
 * @Date 2020/2/20 19:30
 * @Version 1.0
 **/
public class ChinaTelecom implements operator {
    @Override
    public void accessToTheNetwork() {
        System.out.println("中國電信");
    }
}
           

(2)建立一個代理類PhoneProxy 并實作operator 接口

/**
 * @author yly
 * @ClassName PhoneProxy
 * @Date 2020/2/20 19:31
 * @Version 1.0
 **/
public class PhoneProxy implements operator {

    private ChinaTelecom chinaTelecom;

    public PhoneProxy(ChinaTelecom chinaTelecom) {
        this.chinaTelecom = chinaTelecom;
    }

    @Override
    public void accessToTheNetwork() {
        System.out.println("代理開始");
        chinaTelecom.accessToTheNetwork();
        System.out.println("代理完成");
    }
}

           

(3)使用代理對象通路被代理的類

/**
 * @author yly
 * @ClassName demo
 * @Date 2020/2/20 19:33
 * @Version 1.0
 **/
public class demo {
    public static void main(String[] args) {
        PhoneProxy phoneProxy = new PhoneProxy(new ChinaTelecom());
        phoneProxy.accessToTheNetwork();
    }
}
           

(4)運作結果

代理開始
中國電信
代理完成
           

靜态代理優點:

  • 不修改目标類功能的前提下,能通過代理對象對目标功能進行擴充。

靜态代理的缺點:

  • 代理對象需要與被代理對象同時實作一樣的接口,是以會有很多代理類
  • 接口增加方法,代理類與被代理類都需要修改代碼

二、建立動态代理的步驟與靜态代理一緻

(1)建立一個operator 接口和實作operator 接口的實體類ChinaTelecom

/**
 * @author yly
 * @ClassName ChinaTelecom
 * @Date 2020/2/20 19:28
 * @Version 1.0
 **/
public interface operator {
   public void accessToTheNetwork();
   public void play(String name);
}
           
/**
 * @author yly
 * @ClassName ChinaTelecom
 * @Date 2020/2/20 19:30
 * @Version 1.0
 **/
public class ChinaTelecom implements operator {
    @Override
    public void accessToTheNetwork() {
        System.out.println("中國電信");
    }

    @Override
    public void play(String name) {
        System.out.println("玩遊戲:"+name);
    }
}

           

(2)建立一個代理類PhoneProxy ,此處使用JDK動态代理,不需要實作接口

/**
 * @author yly
 * @ClassName PhoneProxy
 * @Date 2020/2/20 19:31
 * @Version 1.0
 **/
public class PhoneProxy {

    private Object object;

    public PhoneProxy(Object object) {
        this.object = object;
    }

    /**
     * @return
     * @CallerSensitive
     * public static Object newProxyInstance(ClassLoader loader, //指定目前目标對象使用的類加載器,擷取加載器方法
     *                                       Class<?>[] interfaces,//目标對象實作的接口類型,使用泛型方法确認類型
     *                                       InvocationHandler h) //事情處理,執行目标對象方法時,觸發事情處理器方法,将目前執行的目标對象方法作為參數傳入
     * throws IllegalArgumentException
     * {}
     */
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(object.getClass().getClassLoader(),
                object.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("JDK動态代理開始");
                        Object returnVal = method.invoke(object, args);
                        System.out.println("JDK動态代理送出");
                        return returnVal;
                    }
                });
    }
}
           

(3)使用代理對象通路被代理的類

/**
 * @author yly
 * @ClassName demo
 * @Date 2020/2/21 0:49
 * @Version 1.0
 **/
public class demo {
    public static void main(String[] args) {
        //建立目标對象
        ChinaTelecom chinaTelecom = new ChinaTelecom();
        //建立代理對象
        operator proxyInstance = (operator)new PhoneProxy(chinaTelecom).getProxyInstance();

        //proxyInstance=class com.sun.proxy.$Proxy0記憶體中生成的代理對象
        System.out.println("proxyInstance="+proxyInstance.getClass());

        proxyInstance.play("王者榮耀");
    }
}
           

(4)運作結果

proxyInstance=class com.sun.proxy.$Proxy0
JDK動态代理開始
玩遊戲:王者榮耀
JDK動态代理送出
           

三、Cglib代理

Cglib可以使用目标對象子類來進行代理,不需要實作接口,可以彌補靜态代理和JDK動态代理的不足,因為靜态代理和JDK動态代理都需要目标對象實作一個接口,如果目标對象隻是一個單獨的對象,沒有實作接口,就可以使用Cglib代理。Cglib被廣泛的用于aop架構中,例如Spring AOP。

Cglib代理模式注意事項

(1):需要引入cglib的jar檔案

(2):在記憶體中動态建立子類,代理的類不能為final,否則報錯。

(3):目标對象方法如果是final或者static時,不會執行目标對象額外的業務方法

Cglib實作步驟

(1):建立目标對象不需要實作接口

(2):建立代理類

(1)建立目标對象不需要實作接口

/**
 * @author yly
 * @ClassName ChinaTelecom
 * @Date 2020/2/21 10:56
 * @Version 1.0
 **/
public class ChinaTelecom {

    public final String play(String name) {
        System.out.println("動态代理cglib,不實作接口");
        return "玩遊戲" + name;
    }
}
           

(2)建立代理類,實作MethodInterceptor接口,重寫 intercept()方法

/**
 * @author yly
 * @ClassName PhoneProxy
 * @Date 2020/2/21 10:58
 * @Version 1.0
 **/
public class PhoneProxy implements MethodInterceptor {
    private Object object;

    public PhoneProxy(Object object) {
        this.object = object;
    }

    public Object getProxyInstance() {
        //建立一個工具類
        Enhancer enhancer = new Enhancer();
        //設定父類
        enhancer.setSuperclass(object.getClass());
        //設定回調函數
        enhancer.setCallback(this);

        return enhancer.create();
    }

    /**
     * 重寫intercept方法,會調用目标對象的方法
     * @param o
     * @param method
     * @param objects
     * @param methodProxy
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("Cglib代理開始");
        Object invoke = method.invoke(object, objects);
        System.out.println("Cglib代理結束");
        return invoke;
    }
}
           

(3)使用代理對象通路被代理的類

/**
 * @author yly
 * @ClassName demo
 * @Date 2020/2/21 11:06
 * @Version 1.0
 **/
public class demo {
    public static void main(String[] args) {
        ChinaTelecom chinaTelecom = new ChinaTelecom();
        ChinaTelecom proxyInstance =(ChinaTelecom) new PhoneProxy(chinaTelecom).getProxyInstance();
        String play = proxyInstance.play("王者榮耀");
        System.out.println(play);

    }
}
           

(4)運作結果

Cglib代理開始
動态代理cglib,不實作接口
Cglib代理結束
玩遊戲王者榮耀
           

将目标方法play()用final修飾時,執行結果為

動态代理cglib,不實作接口
玩遊戲王者榮耀
           

代理模式的使用場景

  • 防火牆代理(内網通過代理穿透防火牆,實作對公網的通路)
  • 遠端代理(通過遠端代理,可以将遠端對象當本地對象調用)
  • 緩存代理(請求資源檔案時,先到緩存拿資料,如果找到資源則傳回,如果找不到資源,再從伺服器拿資源)

使用靜态代理實作緩存代理

(1)建立File接口

/**
 * @author yly
 * @ClassName Image
 * @Date 2020/2/10 19:02
 * @Version 1.0
 **/
public interface File {
   public File  resource();
}
           

(2)建立File的實作類ReadFile

/**
 * @author yly
 * @ClassName RealImage
 * @Date 2020/2/10 19:03
 * @Version 1.0
 **/
public class ReadFile implements File {

    private File file;

    private String fileName;

    public ReadFile(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public File resource() {
        System.out.println("緩存檔案: " + fileName);
        return file;
    }

    public File getResource(String fileName){
        System.out.println("資源檔案: " + fileName);
        return file = new ReadFile(fileName);
    }

}
           

(3)建立代理類ProxyFile

/**
 * @author yly
 * @ClassName ProxyImage
 * @Date 2020/2/10 19:05
 * @Version 1.0
 **/
public class ProxyFile implements File {

    private ReadFile readFile;
    private String fileName;

    public ProxyImage(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public File resource() {
        if(readFile == null){
            readFile = new ReadFile(fileName);
           return readFile.getResource(fileName);
        }
        return readFile.resource();
    }
}
           

(4)使用代理對象通路被代理的類

/**
 * @author yly
 * @ClassName ProxyPatternDemo
 * @Date 2020/2/10 19:06
 * @Version 1.0
 **/
public class ProxyPatternDemo {
    public static void main(String[] args) {
        File file = new ProxyFile("file.jpg");
        // 檔案将從磁盤加載
        File resource = file.resource();
        System.out.println(resource);

        System.out.println("");
        // 檔案從緩存加載
        File resource1 = file.resource();
        System.out.println(resource1);
    }
}
           

(5)運作結果

資源檔案: file.jpg
com.java.designmode.impl.ReadFile@16d3586

緩存檔案: file.jpg
com.java.designmode.impl.ReadFile@16d3586