Java的代理模式
前言
為其他對象提供一個代理以控制對某個對象的訪問。代理類主要負責為委托了(真實對象)預處理消息、過濾消息、傳遞消息給委托類豹休,代理類不現(xiàn)實具體服務,而是利用委托類來完成服務桨吊,并將執(zhí)行結(jié)果封裝處理威根。
其實就是代理類為被代理類預處理消息、過濾消息并在此之后將消息轉(zhuǎn)發(fā)給被代理類视乐,之后還能進行消息的后置處理洛搀。代理類和被代理類通常會存在關聯(lián)關系(即上面提到的持有的被帶離對象的引用),代理類本身不實現(xiàn)服務佑淀,而是通過調(diào)用被代理類中的方法來提供服務留美。
靜態(tài)代理
需求
假如一個班的同學要向老師交班費,都是通過班長把自己的錢轉(zhuǎn)交給老師伸刃。這里谎砾,班長就是學生的代理。
實現(xiàn)
-
首先捧颅,我們創(chuàng)建一個Person接口棺榔。
這個接口就是學生(被代理類),和班長(代理類)的公共接口隘道,他們都有上交班費的行為。
這樣,學生上交班費就可以讓班長來代理執(zhí)行谭梗。
public interface Person { /** * 上交班費 */ void giveMoney(); }
-
Student類實現(xiàn)Person接口忘晤。Student可以具體實施上交班費的動作。
public class Student implements Person { private String name; public Student(String name) { this.name = name; } @Override public void giveMoney() { System.out.println(name+"上交班費20元"); } }
-
StudentsProxy類激捏,這個類也實現(xiàn)了Person接口设塔,但是還另外持有一個學生類對象,由于實現(xiàn)了Peson接口远舅,
同時持有一個學生對象闰蛔,那么他可以代理學生類對象執(zhí)行上交班費(執(zhí)行giveMoney()方法)行為。
public class StudentProxy implements Person { /** * 被代理的學生對象 */ Student student; public StudentProxy(Person person) { // 只代理學生對象 if (person.getClass() == Student.class){ this.student = (Student) person; } } @Override public void giveMoney() { System.out.println("由代理完成上交班費"); student.giveMoney(); } }
-
測試
public class StaticProxyTest { public static void main(String[] args) { //被代理的學生張三图柏,他的班費上交有代理對象monitor(班長)完成 Person zhangsan = new Student("張三"); //生成代理對象序六,并將張三傳給代理對象 Person monitor = new StudentProxy(zhangsan); //班長代理上交班費 monitor.giveMoney(); } }
結(jié)果:
由代理完成上交班費
張三上交班費20元
總結(jié)
這里并沒有直接通過張三(被代理對象)來執(zhí)行上交班費的行為,而是通過班長(代理對象)來代理執(zhí)行了蚤吹。這就是代理模式例诀。
而且,代碼編譯時就確定了被代理的類是哪一個裁着,這里就是Student類繁涂,所以這進一步是靜態(tài)代碼模式。
代理模式最主要的就是有一個公共接口(Person)二驰,一個具體的類(Student)扔罪,一個代理類(StudentsProxy),代理類持有具體類的實例,代為執(zhí)行具體類實例方法桶雀。
動態(tài)代理
簡介
動態(tài)代理矿酵,代理類并不是在Java代碼中定義的,而是在運行時根據(jù)我們在Java代碼中的“指示”動態(tài)生成的背犯。
相比于靜態(tài)代理坏瘩, 動態(tài)代理的優(yōu)勢在于可以很方便的對代理類的函數(shù)進行統(tǒng)一的處理,而不用修改每個代理類中的方法漠魏。
比如說倔矾,想要在代理類中的每個方法前都加上一個處理方法:
在java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler接口,通過這個類和這個接口可以生成JDK動態(tài)代理類和動態(tài)代理對象柱锹。
實現(xiàn)(還是剛剛的需求)
-
新建一個Person接口
public interface Person { /** * 上交班費 */ void giveMoney(); }
-
創(chuàng)建需要被代理的實際類
public class Student implements Person { private String name; public Student(String name) { this.name = name; } @Override public void giveMoney() { try { //假設數(shù)錢花了一秒時間 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(name + "上交班費50元"); } }
-
創(chuàng)建自己的InvocationHandler
public class StuInvocationHandler<T> implements InvocationHandler { T target; public StuInvocationHandler(T target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理執(zhí)行" +method.getName() + "方法"); //代理過程中插入監(jiān)測方法,計算該方法耗時 Instant start = Instant.now(); Object result = method.invoke(target, args); Instant end = Instant.now(); Duration between = Duration.between(start, end); System.out.println("方法執(zhí)行的時間:" + between.toMillis()); return result; } }
測試
public class DynamicProxyTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//1哪自、創(chuàng)建一個InvocationHandler對象
//創(chuàng)建一個實例對象,這個對象是被代理的對象
Person zhangsan = new Student("張三");
//創(chuàng)建一個與代理對象相關聯(lián)的InvocationHandler
InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);
//2禁熏、使用Proxy類的getProxyClass靜態(tài)方法生成一個動態(tài)代理類stuProxyClass
Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});
//3壤巷、獲得stuProxyClass 中一個帶InvocationHandler參數(shù)的構(gòu)造器constructor
Constructor<?> constructor = stuProxyClass.getConstructor(InvocationHandler.class);
//4、通過構(gòu)造器constructor來創(chuàng)建一個動態(tài)實例stuProxy
Person stuProxy = (Person) constructor.newInstance(stuHandler);
stuProxy.giveMoney();
// 方式二瞧毙,通過Proxy.newProxyInstance
Person stuProxy2 = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
stuProxy.giveMoney();
}
}
總結(jié)
先想一下胧华,我們創(chuàng)建了一個需要被代理的學生張三寄症,將zhangsan對象傳給了stuHandler中,我們在創(chuàng)建代理對象stuProxy時矩动,將stuHandler作為參數(shù)了的有巧,上面執(zhí)行代理對象的方法(包括giveMoney)都會被替換成執(zhí)行invoke方法,也就是說悲没,最后執(zhí)行的是StuInvocationHandler中的invoke方法篮迎。
動態(tài)代理的優(yōu)勢在于可以很方便的對代理類的所有方法進行統(tǒng)一的處理,而不用修改代理類中的每個方法示姿。
是因為所有被代理執(zhí)行的方法甜橱,都是通過在InvocationHandler中的invoke方法調(diào)用的,所以我們只要在invoke方法中統(tǒng)一處理栈戳,
就可以對所有被代理的方法進行相同的操作了岂傲。
原理
動態(tài)代理具體步驟:
- 通過實現(xiàn) InvocationHandler 接口創(chuàng)建自己的調(diào)用處理器;
- 通過為 Proxy 類指定 ClassLoader 對象和一組 interface 來創(chuàng)建動態(tài)代理類荧琼;
- 通過反射機制獲得動態(tài)代理類的構(gòu)造函數(shù)譬胎,其唯一參數(shù)類型是調(diào)用處理器接口類型;
- 通過構(gòu)造函數(shù)創(chuàng)建動態(tài)代理類實例命锄,構(gòu)造時調(diào)用處理器對象作為參數(shù)被傳入堰乔。
流程圖