設計模式---代理模式

一. 概述

  1. 代理模式:爲一個對象提供一個替身,以控制對這個對象的訪問。即通過代理對象訪問目標對象.這樣做的好處是:可以在目標對象實現的基礎上,增強額外的功能操作,即擴展目標對象的功能。
  2. 被代理的對象可以是遠程對象、創建開銷大的對象或需要安全控制的對象。
  3. 代理模式有不同的形式, 主要有三種 靜態代理、動態代理 (JDK代理、接口代理)和 Cglib代理 (可以在內存動態的創建對象,而不需要實現接口, 他是屬於動態代理的範疇) 。
  4. 代理模式示意圖
    在這裏插入圖片描述
    二. 靜態代理
        靜態代理在使用時,需要定義接口或者父類,被代理對象(即目標對象)與代理對象一起實現相同的接口或者是繼承相同父類。
  • 具體要求
  1. 定義一個接口:ITeacherDao
  2. 目標對象TeacherDAO實現接口ITeacherDAO
  3. 使用靜態代理方式,就需要在代理對象TeacherDAOProxy中也實現ITeacherDAO
  4. 調用的時候通過調用代理對象的方法來調用目標對象.
  5. 特別提醒:代理對象與目標對象要實現相同的接口,然後通過調用相同的方法來調用目標對象的方法。
  • 類圖:
    在這裏插入圖片描述
  • 代碼示例:
public interface ITeacherDao {
	void teach(); // 授課的方法
}

public class TeacherDao implements ITeacherDao {
	@Override
	public void teach() {
		System.out.println(" 老師授課中  。。。。。");
	}
}
//代理對象,靜態代理
public class TeacherDaoProxy implements ITeacherDao{
	private ITeacherDao target; // 目標對象,通過接口來聚合
	
	//構造器
	public TeacherDaoProxy(ITeacherDao target) {
		this.target = target;
	}

	@Override
	public void teach() {
		System.out.println("開始代理  完成某些操作。。。。。 ");//方法
		target.teach();
		System.out.println("提交。。。。。");//方法
	}
}
public class Client {
	public static void main(String[] args) {
		//創建目標對象(被代理對象)
		TeacherDao teacherDao = new TeacherDao();
		//創建代理對象, 同時將被代理對象傳遞給代理對象
		TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
		//通過代理對象,調用到被代理對象的方法
		//即:執行的是代理對象的方法,代理對象再去調用目標對象的方法 
		teacherDaoProxy.teach();
	}
}
  • 優缺點:
  1. 優點:在不修改目標對象的功能前提下, 能通過代理對象對目標功能擴展。
  2. 缺點:因爲代理對象需要與目標對象實現一樣的接口,所以會有很多代理類。
  3. 一旦接口增加方法,目標對象與代理對象都要維護。

三. 動態代理

  1. 代理對象,不需要實現接口,但是目標對象要實現接口,否則不能用動態代理
  2. 代理對象的生成,是利用JDK的API,動態的在內存中構建代理對象
  3. 動態代理也叫做:JDK代理、接口代理
  • JDK中生成代理對象的API
  1. 代理類所在包:java.lang.reflect.Proxy
  2. JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接收三個參數,完整的寫法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )

在這裏插入圖片描述

  • 代碼示例:將上面的靜態代理改爲JDK動態代理
public interface ITeacherDao {
	void teach(); // 授課方法
	void sayHello(String name);
}

public class TeacherDao implements ITeacherDao {
	@Override
	public void teach() {
		System.out.println(" 老師授課中.... ");
	}

	@Override
	public void sayHello(String name) {
		System.out.println("hello " + name);
	}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyFactory {
	//維護一個目標對象 , Object
	private Object target;

	//構造器 , 對target 進行初始化
	public ProxyFactory(Object target) {
		this.target = target;
	} 
	
	//給目標對象 生成一個代理對象
	public Object getProxyInstance() {
		/*
		 *  public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
            //1. ClassLoader loader : 指定當前目標對象使用的類加載器, 獲取加載器的方法固定
            //2. Class<?>[] interfaces: 目標對象實現的接口類型,使用泛型方法確認類型
            //3. InvocationHandler h : 事情處理,執行目標對象的方法時,會觸發事情處理器方法, 會把當前執行的目標對象方法作爲參數傳入
		 */
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
				target.getClass().getInterfaces(), 
				new InvocationHandler() {
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						System.out.println("JDK代理開始~~");
						//反射機制調用目標對象的方法
						Object returnVal = method.invoke(target, args);
						System.out.println("JDK代理提交");
						return returnVal;
					}
				}); 
	}
}
public class Client {
	public static void main(String[] args) {
		//創建目標對象
		ITeacherDao target = new TeacherDao();
		//給目標對象,創建代理對象, 可以轉成 ITeacherDao
		/**這裏一定要強轉爲接口類型,而不能是具體的實現類,否則會拋出異常: java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to com.h.proxy.HelloServiceImpl,JDK的動態代理就是針對接口的代理
         */
		ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance();
		// proxyInstance=class com.sun.proxy.$Proxy0 內存中動態生成了代理對象
		System.out.println("proxyInstance=" + proxyInstance.getClass());
		//通過代理對象,調用目標對象的方法
		//proxyInstance.teach();
		proxyInstance.sayHello(" tom ");
	}
}
  • 擴展
           我們知道Spring中的AOP就使用到了代理模式,且針對代理的切入點不同,還細分爲前置增強(beforeAdvice),後置增強(afterAdvice),環繞增強(aroundAdvice)等。那麼我們可以簡單的模擬下Spring的前置和後置增強嗎?
// 前置增強
public interface BeforeAdvise {
    void before();
}

// 後置增強
public interface AfterAdvise {
    void after();
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Objects;

public class JDKDynamicProxy implements InvocationHandler{
    /**
     * 代理目標
     */
     private Object target;

    /**
     * 前置增強
     */
    private BeforeAdvise beforeAdvise;

    /**
     * 後置增強
     */
    private AfterAdvise afterAdvise;

    /**
     * 構造函數注入代理目標類以及前/後置增強類
     */
    public JDKDynamicProxy(Object target, BeforeAdvise beforeAdvise, AfterAdvise afterAdvise) {
        this.target = target;
        this.beforeAdvise = beforeAdvise;
        this.afterAdvise = afterAdvise;
    }

    /**
     * 獲取被代理後的對象,最終幹活的
     * @param <T>
     * @return
     */
    public <T> T getProxy( ){
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Objects.nonNull(beforeAdvise)){
            beforeAdvise.before();
        }
        Object result = method.invoke(target, args);
        if (Objects.nonNull(afterAdvise)){
            afterAdvise.after();
        }
        return result;
    }
}
public class Client {
    public static void main(String[] args) {
        JDKDynamicProxy jdkDynamicProxy = new JDKDynamicProxy(new TeacherDao(), new BeforeAdvise() {
            @Override
            public void before() {
                System.out.println("Before");
            }
        }, new AfterAdvise() {
            @Override
            public void after() {
                System.out.println("After");
            }
        });

        ITeacherDao proxy = jdkDynamicProxy.getProxy();
    }
}

四. Cglib代理

  1. 靜態代理和JDK代理模式都要求目標對象是實現一個接口,但是有時候目標對象只是一個單獨的對象,並沒有實現任何的接口,這個時候可使用目標對象子類來實現代理-這就是Cglib代理。
  2. Cglib代理也叫作子類代理,它是在內存中構建一個子類對象從而實現對目標對象功能擴展, 有些書也將Cglib代理歸屬到動態代理。
  3. Cglib是一個強大的高性能的代碼生成包,它可以在運行期擴展java類與實現java接口.它廣泛的被許多AOP的框架使用,例如Spring AOP,實現方法攔截
  4. 在AOP編程中如何選擇代理模式:
    目標對象需要實現接口,用JDK代理
    目標對象不需要實現接口,用Cglib代理
  5. Cglib包的底層是通過使用字節碼處理框架ASM來轉換字節碼並生成新的類。

在這裏插入圖片描述

  • 代碼示例:
  1. 需要引入cglib的jar文件
    在這裏插入圖片描述
  2. 在內存中動態構建子類,注意代理的類不能爲final,否則報錯java.lang.IllegalArgumentException。
  3. 目標對象的方法如果爲final/static,那麼就不會被攔截,即不會執行目標對象額外的業務方法。
public class TeacherDao {
	public String teach() {
		System.out.println(" 老師授課中  , 我是cglib代理,不需要實現接口 ");
		return "hello";
	}
}
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class ProxyFactory implements MethodInterceptor {
	//維護一個目標對象
	private Object target;
	
	//構造器,傳入一個被代理的對象
	public ProxyFactory(Object target) {
		this.target = target;
	}

	//返回一個代理對象:  是 target 對象的代理對象
	public Object getProxyInstance() {
		//1. 創建一個工具類
		Enhancer enhancer = new Enhancer();
		//2. 設置父類
		enhancer.setSuperclass(target.getClass());
		//3. 設置回調函數
		enhancer.setCallback(this);
		//4. 創建子類對象,即代理對象
		return enhancer.create();
	}

	//重寫  intercept 方法,會調用目標對象的方法
	@Override
	public Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) throws Throwable {
		System.out.println("Cglib代理模式 ~~ 開始");
		Object returnVal = method.invoke(target, args);
		System.out.println("Cglib代理模式 ~~ 提交");
		return returnVal;
	}
}
public class Client {
	public static void main(String[] args) {
		//創建目標對象
		TeacherDao target = new TeacherDao();
		//獲取到代理對象,並且將目標對象傳遞給代理對象
		TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(target).getProxyInstance();
		//執行代理對象的方法,觸發intecept 方法,從而實現 對目標對象的調用
		String res = proxyInstance.teach();
		System.out.println("res=" + res);
	}
}
  • 擴展:與上面類似,實現前/後置增強。
@FunctionalInterface
public interface BeforeAdvise {
    void before();
}

@FunctionalInterface
public interface AfterAdvise {
    void after();
}
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.Objects;

public class CGLibDynamicProxy  implements MethodInterceptor{

    public <T>T getProxy(Class<T> cls){
        return (T) Enhancer.create(cls,this);
    }

    public static CGLibDynamicProxy getInatance(){
        return SingletonHolder.instance;
    }

    /**
     * 私有化構造方法
     */
    private CGLibDynamicProxy(){}

    /**
     * 類級的內部類,該內部類的實例與外部類的實例沒有綁定關係,
     * 而且只有被調用到纔會裝載,從而實現了延遲加載
     */
    private static class SingletonHolder{
        /**
         * 靜態初始化器,由JVM來保證線程安全
         * 靜態成員僅被 JVM 加載一次,可以確保實例的唯一性。
         */
        private static CGLibDynamicProxy instance = new CGLibDynamicProxy();
    }

    /**
     * 前置增強
     */
    private BeforeAdvise beforeAdvise;

    /**
     * 後置增強
     */
    private AfterAdvise afterAdvise;

    public void setBeforeAdvise(BeforeAdvise beforeAdvise) {
        this.beforeAdvise = beforeAdvise;
    }

    public void setAfterAdvise(AfterAdvise afterAdvise) {
        this.afterAdvise = afterAdvise;
    }

    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        if (Objects.nonNull(beforeAdvise)){
            beforeAdvise.before();
        }
        Object result = methodProxy.invokeSuper(target,objects);
        if (Objects.nonNull(afterAdvise)){
            afterAdvise.after();
        }
        return result;
    }
}
public class Client {
	public static void main(String[] args) {
		CGLibDynamicProxy dynamicProxy = CGLibDynamicProxy.getInatance();
		dynamicProxy.setBeforeAdvise(() ->System.out.println("Before"));
		dynamicProxy.setAfterAdvise(() -> System.out.println("After"));
		TeacherDao proxy = dynamicProxy.getProxy(TeacherDao.class);
		proxy.teach();
	}
}
  • JDK動態代理 & Cglib代理
    JDK默認的動態代理就是接口的代理,所以必須有接口。cglib是針對類的,本質是繼承了你的目標類,作爲其子類進行方法調用的.但是注意,final 類是不能有子類的,而 CGLib 動態代理恰恰是要去生成這個子類,所以 CGLib 無法對 final 類進行動態代理,運行時會報錯。此外,對於 final 方法是無效的,此時不會報錯。

五. 代理模式(Proxy)的變體

  1. 防火牆代理
    內網通過代理穿透防火牆,實現對公網的訪問。
  2. 緩存代理
    比如:當請求圖片文件等資源時,先到緩存代理取,如果取到資源則ok,如果取不到資源,再到公網或者數據庫取,然後緩存。
  3. 遠程代理
    遠程對象的本地代表,通過它可以把遠程對象當本地對象來調用。遠程代理通過網絡和真正的遠程對象溝通信息。
    在這裏插入圖片描述
  4. 同步代理:主要使用在多線程編程中,完成多線程間同步工作

推薦文章:
Proxy 那點事兒:https://my.oschina.net/huangyong/blog/159788

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