1. 程式人生 > >java學習之代理(2):靜態代理和動態代理

java學習之代理(2):靜態代理和動態代理

一,代理的概念

代理是一個物件,代理物件為其他物件提供一種代理,以控制對這個物件的訪問,代理物件起到中介作用,可以去掉或者增加額外的服務。

如:火車票代售點就是火車站售票處的一個代理物件,可通過訪問代售點進行業務處理。

二,靜態代理的2種實現方式:繼承和聚合

靜態代理中的代理和被代理物件在代理之前關係是確定的。它們都實現了相同的介面或者繼承相同的抽象類。

下面的例子分別講述了所有的類實現同一個介面,類物件之間是如何代理的。

1,繼承

首先定義一個Movable介面

package com.jimmy.proxy;

//首先定義一個介面,接口裡面有一個Move函式
public interface Movable {
	void Move();
}
定義一個Car類,首先介面,並實現Move函式
package com.jimmy.proxy;

import java.util.Random;

public class Car implements Movable{

	@Override
	public void Move() {
		//業務程式碼		
		try {
			Thread.sleep(new Random().nextInt(1000));  //隨機睡一會
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
定義一個Car2類,繼承自Car,並重寫Move()方法
package com.jimmy.proxy;

public class Car2 extends Car{
	@Override
	public void Move() {
		long startTime = System.currentTimeMillis();
		System.out.println("start moving..");
		super.Move();  //呼叫父類的Move()方法,這裡體現了代理的思想。前後加上自己的業務程式碼。
		long endTime = System.currentTimeMillis();
		System.out.println("end move"+" time:"+(endTime-startTime));
	}
}

定義一個Car3類,繼承自Car2,並重寫Move()方法
package com.jimmy.proxy;

public class Car3 extends Car2 {
	@Override
	public void Move() {
		System.out.println("starting log");
		super.Move(); //同樣,呼叫父類的Move()方法。前後加上自己的業務程式碼
		System.out.println("end log");
	}
}

現在測試上面3個類的代理關係
package com.jimmy.proxy;

public class MovableClient {
	public static void main(String[] args) {
		Movable mm = new Car3();  
		mm.Move();  //執行Car3類的Move()方法,但是都訪問到了Car和Car2類的Move()方法
			    //於是,Car3的物件就是Car和Car2物件的代理物件。
	}
}

2,聚合

一個類中用另一個類的物件做成員變數。

首先還是定義Movable介面(同繼承)

package com.jimmy.proxy;

//首先定義一個介面,接口裡面有一個Move函式
public interface Movable {
	void Move();
}

同樣定義一個Car類(同繼承)
package com.jimmy.proxy;

import java.util.Random;

public class Car implements Movable{

	@Override
	public void Move() {
		//業務程式碼		
		try {
			Thread.sleep(new Random().nextInt(1000));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

定義一個時間代理類CarTimeProxy,同樣實現Movable介面
package com.jimmy.proxy;

public class CarTimeProxy implements Movable{
	Movable cc = null;   //接收一個實現了Movable介面的類物件作為變數,也就是所謂的聚合
	public CarTimeProxy(Movable cc) {
		this.cc = cc;
	}

	@Override
	public void Move() {
		long startTime = System.currentTimeMillis();
		System.out.println("start moving..");
		cc.Move();  //呼叫被聚合物件的Move()方法,前後加上自己的業務程式碼。
		long endTime = System.currentTimeMillis();
		System.out.println("end move"+" time:"+(endTime-startTime));
	}
}

定義一個日誌代理類CarLogProxy,同樣實現Movable介面
package com.jimmy.proxy;

public class CarLogProxy implements Movable{
	Movable cc = null;  //接收一個實現了Movable介面的類物件作為變數,也就是所謂的聚合
	public CarLogProxy(Movable cc) {
		this.cc = cc;
	}

	@Override
	public void Move() {
		System.out.println("start logging..");
		cc.Move();  //呼叫被聚合物件的Move()方法,前後加上自己的業務程式碼。
		System.out.println("end log");
	}
}

現在來看,Car類,CarTimeProxy類和CarLogProxy類是平行的,均實現了Movable介面。

CarTimeProxy類和CarLogProxy類中拿到了Car類的物件。

下面測試他們之間的代理關係。

package com.jimmy.proxy;

public class MovableClient {
	public static void main(String[] args) {
		Car cc = new Car();                   //首先獲得Car物件cc
		Movable mm1 = new CarTimeProxy(cc);   //將cc傳入CarTimeProxy類,於是mm1就是cc的代理物件
		Movable mm2 = new CarLogProxy(mm1);   //將mm1傳入CarLogProxy類,於是mm2就是mm1和cc的代理物件
		mm2.Move();
	}
}

3,靜態代理中的繼承和聚合那個更好?存在的問題

聚合好,代理有時需要疊加並且疊加順序不一樣,如:一種情況是先代理1,再代理2,最後代理3。另一種情況是先代理2,再代理3,最後代理1等等。

對於繼承來說,因為繼承模式下,代理的順序都是寫死了的,針對每一種情況,都要寫一個繼承類。類的個數將無限制增加。

對於聚合來說就靈活很多了,只需要根據業務邏輯,由內到外一層一層建立物件即可。

但是,現在我有其他的繼承自Movable介面的類(如:bike類,motocycle類等等)想實現時間和日誌代理,同樣要寫一系列代理類出來。類的個數將同樣無限制增加。

要解決以上問題,就引入了動態代理。

二,動態代理

由靜態代理我們知道,代理的部分由介面,代理物件和代理邏輯組成。

而在靜態代理中,這些部分都是寫死的,不能靈活變換。現在動態代理將這些部分分離出來,然後組合使用。

動態代理相比於靜態代理:不需要編寫代理類的程式碼,只需呼叫函式即可實現代理。靈活性高。

動態代理的使用比較套路。來看使用:

jdk的動態代理由java.lang.reflect.InvocationHandler介面和java.lang.reflect.Proxy類組成。

1,首先定義一個Movable介面

package com.jimmy.dynamicProxy;

public interface Moveable {
	public void run(double speed, String destnation);  //同靜態代理
}


2,定義一個類,實現Movable介面
package com.jimmy.dynamicProxy;

public class Car implements Moveable{

	@Override
	public void run(double speed,String destnation) {
		System.out.println("running with speed:"+speed+"km/h"+" drive to "+destnation);
	}
}


3,現在我們就不用再寫代理類了,直接由函式得到代理物件,然後直接使用。

package com.jimmy.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Proxy.newProxyInstance(loader, interfaces, h)函式返回一個Object型別的物件proxy
 * 第1個引數loader:指定代理物件的類載入器,跟被代理物件的類載入器是一樣的,所以固定寫法為:cc.getClass().getClassLoader()
 * 第2個引數interfaces:指定代理物件實現的介面,因為代理物件和被代理物件要實現相同的介面,所以固定寫為:cc.getClass().getInterfaces()
 * 第3個引數h:是InvocationHandler介面的物件,該介面只定義了一個invoke(Object proxy, Method method, Object[] args)方法,所以在例項化該介面時要實現該方法
 * 
 * invoke(Object proxy, Method method, Object[] args)方法的三個引數:
 * 第1個引數proxy:指代理物件本身,一般用不到
 * 第2個引數method:指被代理物件的函式,被代理物件的函式通過反射被呼叫。所以寫法固定。
 * 第3個引數args:是被代理物件函式的引數,直接加在invoke()方法的引數即可。
 * 
 * 重點是method.invoke(cc, args)前後的程式碼,是代理物件對被代理物件函式功能的擴充套件。
 * 
 * 由此可見動態代理的好處真的是很大,它解耦和了代理和被代理物件的複雜關係。具有高擴充套件性。
 * 
 * 看下面2個代理物件。
 * 
 * 
 */
public class CarProxy {
	public static void main(String[] args) {
		final Moveable cc = new Car();  //得到被代理的物件cc
		
		//得到代理物件proxy
		Moveable proxy = (Moveable) Proxy.newProxyInstance(cc.getClass().getClassLoader(), 
							   cc.getClass().getInterfaces(), 
							   new InvocationHandler() {
								
								@Override
								public Object invoke(Object proxy, Method method, Object[] args)
										throws Throwable {
									System.out.println("準備出發。。。");  //前後增加的內容
									method.invoke(cc, args); //proxy呼叫cc的函式時,由該invoke方法反射執行。
									System.out.println("車停了。。。");
									return null;
								}
							});
		proxy.run(120, "home");//代理物件呼叫被代理物件的方法,就會執行介面中的invoke方法,那麼,增加的代理內容和被代理物件的方法都會執行。
		
		
		
		Moveable proxy2 = (Moveable) Proxy.newProxyInstance(cc.getClass().getClassLoader(), 
				   cc.getClass().getInterfaces(), 
				   new InvocationHandler() {
					
					@Override
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						System.out.println("準備維修。。。");
						method.invoke(cc, args);
						System.out.println("修好了。。。");
						return null;
					}
				});
		proxy2.service();
	}
}