1入蛆、代理模式
即Proxy Pattern撇眯,23種java常用設(shè)計(jì)模式之一谱仪。代理模式的定義:對(duì)其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪(fǎng)問(wèn)蓄髓。
1.1 介紹
代理模式的主要作用是為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪(fǎng)問(wèn)。在某些情況下涯曲,一個(gè)對(duì)象不想或者不能直接引用另一個(gè)對(duì)象野哭,而代理對(duì)象可以在客戶(hù)端和目標(biāo)對(duì)象之間起到中介的作用。
代理模式的思想是為了提供額外的處理或者不同的操作而在實(shí)際對(duì)象與調(diào)用者之間插入一個(gè)代理對(duì)象幻件。這些額外的操作通常需要與實(shí)際對(duì)象進(jìn)行通信拨黔。
1.2 場(chǎng)景舉例
假設(shè)有一組對(duì)象都實(shí)現(xiàn)同一個(gè)接口,實(shí)現(xiàn)同樣的方法绰沥,但這組對(duì)象中有一部分對(duì)象需要有單獨(dú)的方法篱蝇,傳統(tǒng)的笨辦法是在每一個(gè)應(yīng)用端都加上這個(gè)單獨(dú)的方法,但是代碼重用性低徽曲,耦合性高零截。如果用代理的方法則很好的解決了這個(gè)問(wèn)題。
1.3 涉及到的角色
代理模式是給指定對(duì)象提供代理對(duì)象秃臣。由代理對(duì)象來(lái)控制具體對(duì)象的引用涧衙。代理模式涉及到的角色如下:
- 抽象主題角色
聲明了代理主題和真實(shí)主題的公共接口,使任何需要真實(shí)主題的地方都能用代理主題代替奥此。 - 代理主題角色
含有真實(shí)主題的引用弧哎,從而可以在任何時(shí)候操作真實(shí)主題,代理主題功過(guò)提供和真實(shí)主題相同的接口稚虎,使它可以隨時(shí)代替真實(shí)主題撤嫩。代理主題通過(guò)持有真實(shí)主題的引用,不但可以控制真實(shí)主題的創(chuàng)建或刪除蠢终,可以在真實(shí)主題被調(diào)用前進(jìn)行攔截序攘,或在調(diào)用后進(jìn)行某些操作茴她。 - 真實(shí)代理對(duì)象
定義了代理角色所代表的具體對(duì)象。
1.4 分類(lèi)
按照代理創(chuàng)建的時(shí)期代理類(lèi)可以分成兩種:
- 靜態(tài)代理
由程序員創(chuàng)建或特定工具自動(dòng)生成源代碼程奠,再對(duì)其編譯丈牢。在程序運(yùn)行前,代理類(lèi)的.class文件就已經(jīng)存在 了梦染。 - 動(dòng)態(tài)代理
在程序運(yùn)行時(shí)赡麦,運(yùn)用反射機(jī)制動(dòng)態(tài)創(chuàng)建而成朴皆。
1.4.1 靜態(tài)代理的例子
接下來(lái)帕识,介紹一個(gè)代理模式使用的例子。在該例中遂铡,通過(guò)使用代理模式肮疗,沒(méi)有改動(dòng)Person類(lèi)的代碼,實(shí)現(xiàn)了給Person類(lèi)中方法添加日志的功能扒接。一方面伪货,如果有多個(gè)和Person類(lèi)似的實(shí)現(xiàn)了PersonInterface的類(lèi),都可以通過(guò)該代理類(lèi)實(shí)現(xiàn)日志功能钾怔。另一方面碱呼,也實(shí)現(xiàn)了業(yè)務(wù)代碼的解耦,Person類(lèi)專(zhuān)注于業(yè)務(wù)代碼的實(shí)現(xiàn)宗侦,可維護(hù)性和可讀性都更好愚臀。具體如下。
PersonInterface.java:
package com.aop;
/**
* Created by chengxia on 2019/3/30.
*/
public interface PersonInterface {
public void run();
public void eat();
}
Person.java:
package com.aop;
/**
* Created by chengxia on 2019/3/30.
*/
public class Person implements PersonInterface {
public Person(String name) {
this.name = name;
}
private String name;
public void run()
{
System.out.println(name + ": " + "i am running...");
}
public void eat()
{
System.out.println(name + ": " + "i am eating...");
}
}
PersonInterfaceProxy.java:
package com.aop;
/**
* Created by chengxia on 2019/4/1.
*/
public class PersonInterfaceProxy implements PersonInterface {
PersonInterface p;
public PersonInterfaceProxy(PersonInterface p) {
this.p = p;
}
@Override
public void eat() {
System.out.println("Log: begin to eat.");
p.eat();
System.out.println("Log: eat over.");
}
@Override
public void run() {
System.out.println("Log: begin to run.");
p.run();
System.out.println("Log: run over.");
}
}
TestProxy.java:
package com.aop;
/**
* Created by chengxia on 2019/3/30.
*/
public class TestProxy {
public static void main(String []args){
//新建一個(gè)person類(lèi)
PersonInterface p = new Person("Kobe");
//新建一個(gè)person代理
PersonInterfaceProxy pProxy = new PersonInterfaceProxy(p);
pProxy.eat();
pProxy.run();
}
}
運(yùn)行結(jié)果:
Log: begin to eat.
Kobe: i am eating...
Log: eat over.
Log: begin to run.
Kobe: i am running...
Log: run over.
Process finished with exit code 0
1.4.2 動(dòng)態(tài)代理介紹
由靜態(tài)代理的代碼可以看到靜態(tài)代理只能對(duì)一個(gè)接口進(jìn)行服務(wù)矾利,如果項(xiàng)目中有很多個(gè)接口姑裂,那么肯定會(huì)產(chǎn)生過(guò)多的代理。這時(shí)候就需要用到動(dòng)態(tài)代理男旗,由一個(gè)代理類(lèi)完成所有的代理功能舶斧。
動(dòng)態(tài)代理類(lèi)的字節(jié)碼在程序運(yùn)行時(shí)由Java反射機(jī)制動(dòng)態(tài)生成,無(wú)需程序員手工編寫(xiě)它的源代碼察皇。動(dòng)態(tài)代理類(lèi)不僅簡(jiǎn)化了編程工作茴厉,而且提高了軟件系統(tǒng)的可擴(kuò)展性,因?yàn)镴ava什荣。反射機(jī)制可以生成任意類(lèi)型的動(dòng)態(tài)代理類(lèi)矾缓。
JDK動(dòng)態(tài)代理中包含一個(gè)InvocationHandler接口和一個(gè)Proxy類(lèi)。
(1) invocationHandler接口
public interface InvocationHandler {
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable;
}
其中:
Object porxy: 是被代理的對(duì)象
Method method: 要調(diào)用的方法
Object[] args: 要調(diào)用的方法的參數(shù)
(2)Proxy類(lèi)
Proxy類(lèi)是專(zhuān)門(mén)完成代理的操作類(lèi)溃睹,可以通過(guò)此類(lèi)為一個(gè)或多個(gè)接口動(dòng)態(tài)地生成實(shí)現(xiàn)類(lèi)而账,此類(lèi)提供了如下的操作方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
其中:
ClassLoader loader: 是類(lèi)加載器(在java中主要有三種類(lèi)加載器:Booststrap ClassLoader:此加載器采用C++編寫(xiě),一般開(kāi)發(fā)中是看不到的因篇;Extendsion ClassLoader:用來(lái)進(jìn)行擴(kuò)展類(lèi)的加載泞辐,一般對(duì)應(yīng)的是jrelibext目錄中的類(lèi); AppClassLoader:(默認(rèn))加載classpath指定的類(lèi)笔横,是最常使用的是一種加載器。)
Class<?>[] interfaces: 得到全部接口;
InvocationHandler h: 得到InvocationHandler接口的子類(lèi)實(shí)例;
1.4.3 動(dòng)態(tài)代理例子
下面是個(gè)動(dòng)態(tài)代理的例子咐吼,原始的接口和類(lèi)用的還是上面例子中的吹缔。
DynamicProxyInnvocationHandler.java:
package com.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* Created by chengxia on 2019/4/1.
*/
public class DynamicProxyInnvocationHandler implements InvocationHandler {
private Object target;
public DynamicProxyInnvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
//預(yù)處理邏輯
System.out.println("Before executing " + method.getName());
result = method.invoke(target, args);
//事后處理邏輯
System.out.println("After executing " + method.getName());
return result;
}
}
TestDynamicProxy.java:
package com.aop;
import java.lang.reflect.Proxy;
/**
* Created by chengxia on 2019/3/30.
*/
public class TestDynamicProxy {
public static void main(String []args){
// 產(chǎn)生一個(gè)被代理對(duì)象,一個(gè)person類(lèi)
PersonInterface p = new Person("Kobe");
//
// 將被代理對(duì)象交給InvocationHandler
DynamicProxyInnvocationHandler dpi = new DynamicProxyInnvocationHandler(p);
// 根據(jù)被代理對(duì)象產(chǎn)生一個(gè)代理
PersonInterface pProxied = (PersonInterface) Proxy.newProxyInstance(p.getClass().getClassLoader(), p.getClass().getInterfaces(), dpi);
// 執(zhí)行被代理對(duì)象的方法
pProxied.run();
pProxied.eat();
}
}
運(yùn)行結(jié)果如下:
Before executing run
Kobe: i am running...
After executing run
Before executing eat
Kobe: i am eating...
After executing eat
Process finished with exit code 0
2锯茄、切面編程介紹
上面提到的動(dòng)態(tài)代理厢塘,在java中最常見(jiàn)的應(yīng)用之一就是切面編程。
Aspect Oriented Programming(AOP)肌幽,面向切面編程晚碾,是一個(gè)比較熱門(mén)的話(huà)題。AOP主要實(shí)現(xiàn)的目的是針對(duì)業(yè)務(wù)處理過(guò)程中的切面進(jìn)行提取喂急,它所面對(duì)的是處理過(guò)程中的某個(gè)步驟或階段格嘁,以獲得邏輯過(guò)程中各部分之間低耦合性的隔離效果。
2.1 舉例介紹
比如我們最常見(jiàn)的就是日志記錄了廊移,舉個(gè)例子糕簿,我們現(xiàn)在提供一個(gè)查詢(xún)學(xué)生信息的服務(wù),但是我們希望記錄有誰(shuí)進(jìn)行了這個(gè)查詢(xún)狡孔。如果按照傳統(tǒng)的OOP的實(shí)現(xiàn)的話(huà)懂诗,那我們實(shí)現(xiàn)了一個(gè)查詢(xún)學(xué)生信息的服務(wù)接口(StudentInfoService)和其實(shí)現(xiàn)類(lèi) (StudentInfoServiceImpl.java),同時(shí)為了要進(jìn)行記錄的話(huà)苗膝,那我們?cè)趯?shí)現(xiàn)類(lèi)(StudentInfoServiceImpl.java)中要添加其實(shí)現(xiàn)記錄的過(guò)程殃恒。這樣的話(huà),假如我們要實(shí)現(xiàn)的服務(wù)有多個(gè)呢荚醒?那就要在每個(gè)實(shí)現(xiàn)的類(lèi)都添加這些記錄過(guò)程芋类。這樣做的話(huà)就會(huì)有點(diǎn)繁瑣,而且每個(gè)實(shí)現(xiàn)類(lèi)都與記錄服務(wù)日志的行為緊耦合界阁,違反了面向?qū)ο蟮囊?guī)則侯繁。
那么怎樣才能把記錄服務(wù)的行為與業(yè)務(wù)處理過(guò)程中分離出來(lái)呢?看起來(lái)好像就是查詢(xún)學(xué)生的服務(wù)自己在進(jìn)行泡躯,但卻是背后日志記錄對(duì)這些行為進(jìn)行記錄贮竟,并且查詢(xún)學(xué)生的服務(wù)不知道存在這些記錄過(guò)程,這就是我們要討論AOP的目的所在较剃。AOP的編程咕别,好像就是把我們?cè)谀硞€(gè)方面的功能提出來(lái)與一批對(duì)象進(jìn)行隔離,這樣與一批對(duì)象之間降低了耦合性写穴,可以就某個(gè)功能進(jìn)行編程惰拱。
2.2 實(shí)例:通過(guò)切面編程添加日志功能
通常,Java語(yǔ)言中的切面編程是通過(guò)動(dòng)態(tài)代理來(lái)實(shí)現(xiàn)的啊送。
接下來(lái)舉一個(gè)通過(guò)切面編程給一個(gè)類(lèi)添加日志功能的例子偿短。如下是一個(gè)Person類(lèi)欣孤,定義:
PersonInterface.java:
package com.aop;
/**
* Created by chengxia on 2019/3/30.
*/
public interface PersonInterface {
public void run();
public void eat();
}
Person.java:
package com.aop;
/**
* Created by chengxia on 2019/3/30.
*/
public class Person implements PersonInterface {
public Person(String name) {
this.name = name;
}
private String name;
public void run()
{
System.out.println(name + ": " + "i am running...");
}
public void eat()
{
System.out.println(name + ": " + "i am eating...");
}
}
接下來(lái),通過(guò)切面編程昔逗,給該類(lèi)添加日志功能降传。
首先,編寫(xiě)日志記錄的代碼實(shí)現(xiàn):
LoggingInerface.java:
package com.aop;
import java.lang.reflect.Method;
/**
* Created by chengxia on 2019/3/30.
*/
public interface LoggingInerface {
public void log(Method m);
}
Logging.java:
package com.aop;
import java.lang.reflect.Method;
/**
* Created by chengxia on 2019/3/30.
*/
public class Logging implements LoggingInerface {
public void log(Method m){
System.out.println("Log: " + m.getName() + "Method excuted.");
}
}
接下來(lái)勾怒,通過(guò)java的動(dòng)態(tài)代理婆排,實(shí)現(xiàn)一個(gè)生成帶日志記錄功能Person實(shí)例的工廠(chǎng)方法:
PersonFactory.java:
package com.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Created by chengxia on 2019/3/30.
*/
public class PersonFactory {
//接受目標(biāo)和建議,產(chǎn)生任意類(lèi)(只要該類(lèi)有接口)的代理類(lèi),攔截所有的方法訪(fǎng)問(wèn)
public static Object getPerson(final Object obj,final LoggingInerface log)
{
Object proxy = Proxy.newProxyInstance(PersonFactory.class.getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler()
{
public Object invoke(Object arg0, Method m, Object[] arg2)
throws Throwable
{
log.log(m);
Object value = m.invoke(obj, arg2);
return value;
}
});
return proxy;
}
}
到這里笔链,切面程序就完成了段只,接下來(lái),寫(xiě)一個(gè)類(lèi)測(cè)試:
TestAOP.java:
package com.aop;
/**
* Created by chengxia on 2019/3/30.
*/
public class TestAOP {
public static void main(String []args){
//包含日志記錄的類(lèi)
LoggingInerface log = new Logging();
PersonInterface p = (PersonInterface) PersonFactory.getPerson(new Person("Tom"), log);
p.eat();
p.eat();
p.run();
p.run();
}
}
運(yùn)行結(jié)果如下:
Log: eatMethod excuted.
Tom: i am eating...
Log: eatMethod excuted.
Tom: i am eating...
Log: runMethod excuted.
Tom: i am running...
Log: runMethod excuted.
Tom: i am running...
Process finished with exit code 0
這時(shí)候卡乾,如果我們新建一個(gè)類(lèi)翼悴,也實(shí)現(xiàn)了PersonInterface接口:
People.java:
package com.aop;
/**
* Created by chengxia on 2019/3/30.
*/
public class People implements PersonInterface {
public People(String name) {
this.name = name;
}
private String name;
public void run()
{
System.out.println(name + ": " + "i am running...");
}
public void eat()
{
System.out.println(name + ": " + "i am eating...");
}
}
這個(gè)類(lèi)也可以通過(guò)前面的動(dòng)態(tài)代理,獲得日志功能幔妨。下面是一個(gè)測(cè)試?yán)樱?br> TestAOP.java:
package com.aop;
/**
* Created by chengxia on 2019/3/30.
*/
public class TestAOP {
public static void main(String []args){
//包含日志記錄的類(lèi)
LoggingInerface log = new Logging();
PersonInterface p = (PersonInterface) PersonFactory.getPerson(new People("Chairman"), log);
p.eat();
p.eat();
p.run();
p.run();
}
}
運(yùn)行結(jié)果:
Log: eatMethod excuted.
Chairman: i am eating...
Log: eatMethod excuted.
Chairman: i am eating...
Log: runMethod excuted.
Chairman: i am running...
Log: runMethod excuted.
Chairman: i am running...
Process finished with exit code 0
甚至這里,我們?cè)傩陆ㄒ粋€(gè)Animal類(lèi)谍椅,定義如下:
AnimalInterface.java:
package com.aop;
/**
* Created by chengxia on 2019/3/30.
*/
public interface AnimalInterface {
public void jump();
public void fly();
}
Animal.java:
package com.aop;
/**
* Created by chengxia on 2019/3/30.
*/
public class Animal implements AnimalInterface {
public Animal(String name) {
this.name = name;
}
private String name;
public void jump()
{
System.out.println(name + ": " + "jumping...");
}
public void fly()
{
System.out.println(name + ": " + "flying...");
}
}
這個(gè)類(lèi)误堡,也可以通過(guò)上面的動(dòng)態(tài)代理切面獲得日志功能,下面是要給測(cè)試?yán)樱?/p>
package com.aop;
/**
* Created by chengxia on 2019/3/30.
*/
public class TestAOP {
public static void main(String []args){
//包含日志記錄的類(lèi)
LoggingInerface log = new Logging();
AnimalInterface a = (AnimalInterface) PersonFactory.getPerson(new Animal("Paopao"), log);
a.fly();
a.jump();
}
}
運(yùn)行結(jié)果如下:
Log: flyMethod excuted.
Paopao: flying...
Log: jumpMethod excuted.
Paopao: jumping...
Process finished with exit code 0
可見(jiàn)雏吭,切面還是很強(qiáng)大的锁施。它可以實(shí)現(xiàn)業(yè)務(wù)代碼和技術(shù)日志代碼的分離,使代碼的結(jié)構(gòu)杖们、可讀性悉抵、可維護(hù)性等都更好。
面向切面在英文中的單詞是Aspect Oriented Programming(AOP)摘完,在spring框架中叫aop姥饰,它是可以通過(guò)預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)在不修改源代碼的情況下給程序動(dòng)態(tài)統(tǒng)一添加功能的一種技術(shù)。它是一種新的方法論孝治,它是對(duì)傳統(tǒng)OOP編程的一種補(bǔ)充列粪。