一、最初的設計
有一個經營一家在線商店的客戶,在這個商店系統中,有一個供管理員使用的管理定單的類,代碼如下:
1
、接口
Interface OrderService{
?
?
??????? public boolean showOrders();//
察看定單
}
2、實現
Class OrderServiceImp implements OrderService{
public boolean showOrders(){
?? //
察看所有的定單
}
}
3、定單系統的流程控制類
class OrderSystem{
void processOrder(OrderService service){//
定義了定單處理的流程
//
做其它的業務
……
…...
//
顯示定單
if(service.showOrders(){
//do something
}
else{
//do something
}
……
…...
//
做其它的業務
}
}
4、整個商店系統的主類
public class OnlineShop{
public static void main(String args[]){
//
做其它的業務
……….
………
//
做定單業務
OrderService service = new OrderServiceImp();
OrderSystem.processOrder
(
service);
//
做其它的業務
…….
…...
}
?
}
現在系統的問題是安全性不好,從管理系統登陸的用戶不需要驗證就可以察看定單。所以客戶希望在察看定單之前,能驗證一下身份,
只有授權的用戶才能察看定單。
這個問題很好辦,首先,增加一個類,用于安全驗證:
class SecurityManager{
public static boolean check(){
//
進行身份驗證
//
如果驗證成功,返回
true;
//
如果驗證失敗,返回
false;
}
}
然后,在
showOrders
方法中做如下的更改
Class OrderServiceImp{
public boolean showOrders(){
if(SecurityManager.check()){
//
如果驗證成功
//
顯示定單
return true;
}
else{
//
如果驗證失敗
//
做一些處理失敗的工作
return false;
}
}
}
OrderSystem
類和
OnlineShop
類不需要更改。
好,這樣就搞定了,很容易,不過,總是感覺哪里有些不妥
………
?
二、
Proxy
模式
這個新的系統運行了一段時間,客戶有提了新的需求:
我們有需要另外的一套銷售系統,這個新系統的定單處理子系統在業務流程上和上面提到的定單系統是一模一樣的
…...
聽到這里,我想
"
好,既然新系統的業務流程一樣,那么
OrderSystem
類應該是可以重用的,工作量不大
……."
。
但是,客戶接著說
"
除了新系統不需要安全驗證功能。
"
果然出問題了,原來的
OrderService
中,驗證的代碼和業務代碼都寫在一起了,根本沒法分開,除非再寫一個
OrderService
,他們的代碼大部分都是重復的,只是去掉了驗證的代碼。
原來的設計問題在于:它違反了
單一職責原則
。驗證和顯示定單是不同的兩個職責,
OrderService
應該只負責業務,對于一些附加的功能,例如權限認證應該是一無所知的。如何才能做到這一點呢?
GOF
的書里已經總結了一個解決此類問題的設計模式,就是
代理模式
(Proxy)
。
使用代理模式重新設計的架構如下:
???? 1.
為
OrderServiceImp
建立一個代理類,這個類必須要繼承同樣的接口
OrderService
????? class ProxyOrderService implements OrderService{
???????? private OrderService target;
ProxyOrderService(OrderService target){
this.target = target;
}
public boolean showOrders(){
if(SecurityManager.check()){
//
如果驗證成功
//
顯示定單,具體的工作委托給
OrderServiceImpl
return target.showOrders();
}
else{
//
如果驗證失敗
//
做一些處理失敗的工作
return false;
}
????? }
OrderServiceImp
類只要負責處理業務邏輯即可
,
不必關心例如驗證之類的附加問題,這些問題交給代理來處理好了。而且業務類很容易單獨的重用。
??? class OrderServiceImp implements OrderService{
public boolean showOrders(){
?? //
察看所有的定單
}
??? }
?? 2.
我們把新的商店系統的主類命名為
InnerShop
??? OnlineShop
的
main
方法做如下的修改:
????? public class OnlineShop{
public static void main(String args[]){
//
做其它的業務
……….
………
//
做定單業務
,
帶有驗證
OrderService service = new ProxyOrderService(new OrderServiceImp());
???????? OrderSystem.processOrder
(
service);
??????? //
做其它的業務
?????? …….
?????? …...
}
}
InnerShop
的
main
方法這樣來寫:
public class InnerShop{
public static void main(String args[]){
//
做其它的業務
……….
………
//
做定單業務,不需要驗證
OrderService service = new OrderServiceImp();
OrderSystem.processOrder
(
service);
//
做其它的業務
…….
…...
}
?
}
這樣的話,整個
OrderSystem
類就可以做到重用了。
?
三、新的問題
這個系統運行了一段時間,工作得很好。所以客戶又追加了新的需求,需要在
OrderService
類中增加刪除定單功能,同樣,只有有管理權限的用戶才能刪除定單。有了上面的關于
Proxy
模式的知識,做這個很簡單:
1、在接口中增加一個方法
removeOrder()
Interface OrderService{
public boolean showOrders();//
察看定單
public boolean removeOrder(Order order);//
刪除定單
}
2、然后再實現它
Class OrderServiceImp{
public boolean showOrders(){
//
顯示定單
}
?
public boolean removeOrder(Order order)
{
//
刪除定單,成功返回
true,
失敗返回
false
}
}
3
、修改代理類
class ProxyOrderService implements OrderService{
???????? private OrderService target;
ProxyOrderService(OrderService target){
this.target = target;
}
public boolean showOrders(){
if(SecurityManager.check()){
//
如果驗證成功
//
顯示定單,具體的工作委托給
OrderServiceImpl
return target.showOrders();
}
else{
//
如果驗證失敗
//
做一些處理失敗的工作
return false;
}
?
}
//
新增加的
removeOrder
方法
public boolean removeOrder(){
?if(SecurityManager.check()){
//
如果驗證成功
//
刪除定單,具體的工作委托給
OrderServiceImpl
return target.removeOrder();
?}
?else{
//
如果驗證失敗
//
做一些處理失敗的工作
?
return false;
}
???????
}
???
???}
問題是解決了,不過感覺上不太優雅,而且也存在著潛在的隱患
…..
。
哪里不對呢,看看驗證的代碼:
if(SecurityManager.check()){
如果驗證成功
…...
}
else{
//
如果驗證失敗
…...
}
這樣的代碼重復了兩遍,如果用戶有需要增加業務方法,那么這段代碼還得一遍又一遍的重復。大量的重復代碼就意味著難于維護,而且,一旦某個新的業務方法忘記加上這段代碼,就會造成安全隱患。
除此之外,還有什么隱患呢?想一下如果過幾天客戶有來找你,這次是希望給另一種業務-會員管理系統加上驗證,假定會員管理的接口是
MemberService
,那么我還需要增加一個
ProxyMemberService
,因為
MemberService
的業務方法和
OrderService
完全不同。如果業務逐漸增多,我們會發現我們陷入了一個
Proxy
的海洋里,
?
AServiceImpl---------------ProxyAService
BServiceImpl---------------ProxyBService
……………………………….
………………………………
NServiceImpl--------------ProxyNService
更可怕的是每一個
Proxy
里面,都到處散布著安全驗證代碼片斷。
?
四、最后的設計
現在來整理一下思路,看看問題究竟出在什么地方。
我們再重新審視一下
"
身份驗證
"
這個功能,他在系統中到底處在什么位置呢?
首先,它是與業務邏輯無關的一個單獨的模塊,不應該放在處理業務邏輯的方法中,于是我們用了
Proxy
把它從處理業務邏輯的方法中分離出來了。
但是這種分離并不徹底,因為這個
"
身份驗證
"
功能應該是獨立于
業務
的,不管這類業務是關于定單的還是會員管理的,它應該在后面無聲無息的運行,像
OrderService, MemberService
這些業務類都不應該察覺到身份驗證功能的存在。
?
這樣看來,使用代理模式的思路沒有錯,它確實可以把驗證功能和業務邏輯分開,但是我們還需要更高級些的東西,我希望有這樣一種代理類,它可以做為任何類的代理,即使這些類的接口完全不同。
會有這樣的好東西嗎?從
Java1.3
版開始,提供了一種叫做動態代理的技術,它的特點是可以做為任何類的代理,而且在運行期可以動態的改變被代理的對象,它會攔截被代理類的方法調用,然后可以神不知鬼不覺的在真實的業務方法前后增加一些操作,比如說驗證。那么我們來看看它是怎么實現的:
1
、首先我們需要一個類,它實現了
java.lang.reflect.InvocationHandler
接口,我們可以把它稱作“攔截器
"
,這個接口只定義了一個方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
proxy
是代理類,
method
代表要調用的真實業務類的方法,
args
是方法的參數,這三個參數在運行期會由
JRE
傳入進來,我們只需要這樣做就可以了:
public class
Security
Handler implements InvocationHandler{
private Object target;
//
被代理的類,即業務類
public
Security
Handler(Object target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Boolean checkOk = SecurityManager.check();
if(checkOk){
?? return
method.invoke(target, args);
}
???????? else????
???????? {
?????????? return ret;
???????? }
}
?
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
}
?
2.
我們還需要一個工廠,來提供
Proxy
對象
public class ServiceProxyFactory{
public
static Object getProxy(Class type, InvocationHandler handler) throws Exception{
//type
:要被代理的類的
class
對象
//handler
:要使用的
InvocationHandler
?? //
根據被代理類的類型,得到一個
Proxy
???
Class proxyClass = Proxy.getProxyClass(type.getClassLoader(), new Class[]{type});
?//
得到這個
Proxy
的構造函數
??
Constructor cons = proxyClass.getConstructor(new Class[]{InvocationHandler.class});
//
通過
Proxy
的構造函數得到一個
Proxy
的實例,這個實例就是針對特定業務類的代理類
Object obj = cons.newInstance(new Object[]{handler});
return obj;
}
}
?
3
.
使用這個代理類
在
OnlineShop
的
main
函數中,可以這樣寫:
?
public static void main(String args){
//
建立針對驗證服務的攔截器,并且指定這個攔截器攔截
OrderService
的方法調用
Security
Handler handler = new
SecurityHandler
(new
OrderServiceImp
());
//
得到針對使用驗證攔截器的
OrderService
代理
OrderService
service
= (
OrderService
)
ServiceProxyFactory.
getProxy(
OrderService
.class, handler);
OrderSystem.processOrder
(
service);
}
那么,如果我們需要為會員管理功能提供驗證機能,只需要這樣就可以:
Security
Handler handler = new
SecurityHandler
(new
MemberServiceImp
());
MemberService
service
= (
MemberService
)
ServiceProxyFactory.
getProxy
MemberService
.class, handler);
MemberSystem.processMember
(
service);
?
更近一步,如果客戶有要求我們為整個系統增加記錄日志功能,這個功能和安全驗證功能一樣,都是獨立于業務的,我們只要按照同樣的方法再建立一個
LogHandler
即可:
LogHandler handler = new LogHandler(new OrderServiceimpl());
OrderService
service
= (
OrderService
)
ServiceProxyFactory.
getProxy(
OrderService
.class, handler);
OrderSystem.processOrder
(
service);
?
我們還可以做什么?
我們還可以用一個容器把需要用附加另外的服務的類存放在
XXXHandler
中,還可以在一個
XML
文件中指定哪個
Handler
應用于那些類,哪些業務方法
再遠一些,我們希望攔截做得更透明一些,如果我們不希望看到類似:
OrderService
service
= (
OrderService
)
ServiceProxyFactory.
getProxy(
OrderService
.class, handler);
這樣的代碼,我們還可以設計成采用依賴注入的方式把
Proxy
類悄悄的替換進系統中。
?
最后,我們來總結一下,我們究竟做了些什么,并且用精確的術語對我們所做的事情進行一下定義:
1
.
首先,我們關注了一種問題,驗證或者日志,這樣的問題是散布在系統的各處,獨立與業務的,我們把這種問題叫做一個
"
橫切關注點
"
2.
我們把像驗證這樣的問題集中在一處處理,這樣就形成了一個
"
方面
(ASPECT)
"
3.
然后我們用一個
SecurityHandler
實現了這個
方面,
我們把
SecurityHandler
叫做一個“
增強(
Advice)
”
4.
像
OrderService.showOrders()
這樣需要進行安全認證的方法,我們把它們叫做
“切入點
"
5.
在運行期,
Advice
會被動態的織入到切入點處,透明的增強了原有業務的功能。
上面所做的事情,就可以看做是AOP的機制的一種基本實現。