java學習之代理(2):靜態代理和動態代理
一,代理的概念
代理是一個物件,代理物件為其他物件提供一種代理,以控制對這個物件的訪問,代理物件起到中介作用,可以去掉或者增加額外的服務。
如:火車票代售點就是火車站售票處的一個代理物件,可通過訪問代售點進行業務處理。
二,靜態代理的2種實現方式:繼承和聚合
靜態代理中的代理和被代理物件在代理之前關係是確定的。它們都實現了相同的介面或者繼承相同的抽象類。
下面的例子分別講述了所有的類實現同一個介面,類物件之間是如何代理的。
1,繼承
首先定義一個Movable介面
定義一個Car類,首先介面,並實現Move函式package com.jimmy.proxy; //首先定義一個介面,接口裡面有一個Move函式 public interface Movable { void 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();
}
}