Java設計模式之代理模式

設計模式之代理模式

今天學到Spring的動態代理實現AOP,對代理這個概念很模糊,看了一篇文章發現這是一種設計模式,因而學習記錄一下。html

簡介

代理模式是一種對象結構型的模式,主要爲其餘對象提供一種代理以控制對這個對象的訪問。簡單點說就是你訪問一個對象並非直接的訪問它,而是經過一個代理簡介訪問,那麼這個代理就能夠在訪問對象以前或以後作一些定製化的操做,好比校驗入參,打印日誌什麼的。Spring AOP就是一個代理模式的典型應用。java

java中的代理分爲三類:靜態代理、動態代理和Cglib代理。下面依次講解着三種代理。編程

1. 靜態代理

靜態代理在使用時,須要定義接口或者父類,被代理的對象和代理對象須要一塊兒實現同一個接口或者繼承同一個父類。設計模式

代碼示例緩存

接口:框架

package com.wangjun.designPattern.proxy;

/*
 * 咱們有一我的類的接口,有不少種職業,好比教師,學生
 */
public interface Person {
    //獲取本職業的職責
    public String getDuty();
}

目標對象1:ide

package com.wangjun.designPattern.proxy;

public class Student implements Person {

    @Override
    public String getDuty() {
        return "學習知識";
    }

}

目標對象2:函數

package com.wangjun.designPattern.proxy;

public class Teacher implements Person {

    @Override
    public String getDuty() {
        return "教書育人";
    }

}

代理對象:工具

package com.wangjun.designPattern.proxy;

import java.util.HashMap;
import java.util.Map;

/*
 * 經過代理,加入緩存功能
 */
public class CachedPersonProxy implements Person{

    private Person person;
    private Map<Person,String> map = new HashMap<>();;

    public CachedPersonProxy(Person person) {
        this.person = person;
    }

    @Override
    public String getDuty() {
        String duty = map.get(person);
        if(null == duty) {
            duty = person.getDuty();
            map.put(person, duty);
        }
        return duty;
    }
}

測試類:性能

package com.wangjun.designPattern.proxy;

public class Main {

    public static void main(String[] args) {
        Teacher teacher = new Teacher();

        CachedPersonProxy proxy = new CachedPersonProxy(teacher);

        System.out.println(proxy.getDuty());
    }

}

靜態代理總結:

  1. 優勢:能夠作到在不修改目標對象的功能前提下,對目標功能擴展;
  2. 缺點:由於代理對象須要與目標對象實現同樣的接口,因此會有不少代理類,同時,一旦接口增長方法,目標對象與代理對象都要維護。

2. 動態代理

動態代理也叫JDK代理或者接口代理。

動態代理有如下特色:

  • 代理對象不須要實現接口;
  • 代理對象的生成是利用JDK中的API動態的在內存中構建代理對象(須要咱們指定建立代理對象/目標對象實現的接口的類型);

代理類所在的包是:java.lang.reflect.Proxy

JDK實現動態代理只須要使用newProxyInstance方法,該方法須要傳入三個變量:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
  • ClassLoader loader:指定當前目標對象使用類加載器,獲取加載器的方法是固定的
  • Class<?>[] interfaces:目標對象實現的接口的類型,使用泛型方式確認類型
  • InvocationHandler h:事件處理,執行目標對象的方法時,會觸發事件處理器的方法,會把當前執行目標對象的方法做爲參數傳入。

代碼示例

接口類Person.class和實現類Teacher.class、Sutdent.class不變,在這個基礎上,增長一個代理工廠類ProxyFactory.java,而後在測試類(須要使用到代理的代碼)中先創建目標對象和代理對象的聯繫,接着代用代理對象的中同名方法。

代理工廠類:

package com.wangjun.designPattern.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class ProxyFactory {

    private Person person;
    private Map<Person, String> map = new HashMap<>();

    public ProxyFactory(Person person) {
        this.person = person;
    }

    public Object getProxyInstance() {
        return Proxy.newProxyInstance(
                person.getClass().getClassLoader(), 
                person.getClass().getInterfaces(),
                new InvocationHandler() {

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        String duty = map.get(person);
                        if(null == duty) {
                            //執行對象方法
                            System.out.println("沒有緩存");
                            duty = (String) method.invoke(person, args);
                            map.put(person, duty);
                        }
                        return duty;
                    }
                });
    }

}

測試類:

package com.wangjun.designPattern.proxy;

public class Main {

    public static void main(String[] args) {
        Person teacher = new Teacher();
        System.out.println(teacher.getClass());
        //使用動態代理
        ProxyFactory proxyFactory = new ProxyFactory(teacher);
        Person teacherProxy = (Person) proxyFactory.getProxyInstance();
        System.out.println(teacherProxy.getClass());
        System.out.println(teacherProxy.getDuty());
        System.out.println(teacherProxy.getDuty());
    }
}

總結

代理對象不須要實現接口,可是目標對象必定要實現接口,不然不能用動態代理。

3. Cglib代理

上面的靜態代理和動態代理模式都是要求目標對象是實現一個接口的目標對象,可是有時候目標對象只是一個單獨的對象,並無實現任何的接口,這個時候就可使用以目標對象子類的方式類實現代理,這種方法就叫作:Cglib代理。

  • Cglib是一個強大的,高性能,高質量的Code生成類庫,它能夠在運行期擴展Java類與實現Java接口。它普遍的被許多AOP的框架使用,例如Spring AOP和synaop,爲他們提供方法的interception(攔截)。
  • Cglib代理,也叫做子類代理,它是在內存中構建一個子類對象從而實現對目標對象功能的擴展。
  • Cglib包的底層是經過使用一個小而塊的字節碼處理框架ASM來轉換字節碼並生成新的類。不鼓勵直接使用ASM,由於它要求你必須對JVM內部結構包括class文件的格式和指令集都很熟悉。

Cglib子類實現代理的方法

  1. 須要引入cglib和asm的jar包文件,Spring的核心包裏面已經包含了此包;
  2. 引入jar包後,就能夠在內存中動態構建子類;
  3. 代理的類不能爲final,不然報錯;
  4. 目標對象的方法若是爲final/static,那麼就不會被攔截,即不會執行目標對象額外的方法。

代碼示例

目標對象類:

package com.wangjun.designPattern.proxy;
/*
 * 目標對象,沒有實現任何接口
 */
public class Doctor {
    public String getDuty() {
        return "救死扶傷";
    }
}

Cglib代理工廠類:

package com.wangjun.designPattern.proxy;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/*
 * cglib代理工廠
 * 對Doctor在內存中動態構建一個子類對象
 */
public class CglibProxyFactory implements MethodInterceptor {

    private Object person;
    private Map<Object, String> map = new HashMap<>();

    public CglibProxyFactory(Object person) {
        this.person = person;
    }

    // 給目標對象建立一個代理對象
    public Object getProxyInstance() {
        // 1.工具類
        Enhancer en = new Enhancer();
        // 2.設置父類
        en.setSuperclass(person.getClass());
        // 3.設置回調函數
        en.setCallback(this);
        // 4.建立子類(代理對象)
        return en.create();
    }

    @Override
    public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
        String duty = map.get(person);
        if(null == duty) {
            //執行目標對象的方法
            System.out.println("cglib實現,有緩存");
            duty = (String) arg1.invoke(person, arg2);
            map.put(person, duty);
        }
        return duty;
    }

}

測試類:

package com.wangjun.designPattern.proxy;

public class Main {

    public static void main(String[] args) {
        // 使用cglib
        // 目標對象
        Doctor target = new Doctor();
        System.out.println(target.getClass());
        // 代理對象
        Doctor proxyCglib = (Doctor) new CglibProxyFactory(target).getProxyInstance();
        // 執行代理對象的方法
        System.out.println(proxyCglib.getClass());
        System.out.println(proxyCglib.getDuty());
        System.out.println(proxyCglib.getDuty());
    }
}

在Spring的AOP編程中:

  • 若是加入容器的目標對象有實現接口,用JDK代理
  • 若是目標對象沒有實現接口,用Cglib代理

參考:http://www.javashuo.com/article/p-nzewgjjh-gt.html

相關文章
相關標籤/搜索