一、最初的设计
有一个经营一家在线商店的客户,在这个商店系统中,有一个供管理员使用的管理定单的类,代码如下:
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的机制的一种基本实现。