//张三修改就不会有问题
order.setOrderNum(123, "张三");
//再次输出order
System.out.println("张三修改后,订单记录:"+order);
}
}
运行结果如下:
| 对不起李四,您无权修改订单中的订购数量。 李四修改后订单记录没有变化: productName=设计模式,orderNum=100,orderUser=张三 张三修改后,订单记录:productName=设计模式,orderNum=123,orderUser=张三 |
从上面的运行结果就可以看出,在通过代理转调目标对象的时候,在代理对象里面,对访问的用户进行了权限判断,如果不满足要求,就不会转调目标对象的方法,从而保护了目标对象的方法,只让有权限的人操作。
11.3.3 Java中的代理
Java对代理模式提供了内建的支持,在java.lang.reflect包下面,提供了一个Proxy的类和一个InvocationHandler的接口。
通常把前面自己实现的代理模式,称为Java的静态代理。这种实现方式有一个较大的缺点,就是如果Subject接口发生变化,那么代理类和具体的目标实现都要变化,不是很灵活,而使用Java内建的对代理模式支持的功能来实现则没有这个问题。
通常把使用Java内建的对代理模式支持的功能来实现的代理称为Java的动态代理。动态代理跟静态代理相比,明显的变化是:静态代理实现的时候,在Subject接口上定义很多的方法,代理类里面自然也要实现很多方法;而动态代理实现的时候,虽然Subject接口上定义了很多方法,但是动态代理类始终只有一个invoke方法。这样当Subject接口发生变化的时候,动态代理的接口就不需要跟着变化了。
Java的动态代理目前只能代理接口,基本的实现是依靠Java的反射机制和动态生成class的技术,来动态生成被代理的接口的实现对象。具体的内部实现细节这里不去讨论。如果要实现类的代理,可以使用cglib(一个开源的Code Generation Library)。
还是来看看示例,那就修改上面保护代理的示例,看看如何使用Java的动态代理来实现同样的功能。
(1)订单接口的定义是完全一样的,就不去赘述了。
(2)订单对象的实现,只是添加了一个toString,以方便测试输出,这里也不去示例了。在前面的示例中,toString是实现在代理类里面了。
(3)直接看看代理类的实现,大致有如下变化:
- 要实现InvocationHandler接口
- 需要提供一个方法来实现:把具体的目标对象和动态代理绑定起来,并在绑定好过后,返回被代理的目标对象的接口,以利于客户端的操作
- 需要实现invoke方法,在这个方法里面,去具体判断当前是在调用什么方法,需要如何处理。
示例代码如下:
/**
* 使用Java中的动态代理
*/
public class DynamicProxy implements InvocationHandler{
/**
* 被代理的对象
*/
private OrderApi order = null;
/**
* 获取绑定好代理和具体目标对象后的目标对象的接口
* @param order 具体的订单对象,相当于具体目标对象
* @return 绑定好代理和具体目标对象后的目标对象的接口
*/
public OrderApi getProxyInterface(Order order){
//设置被代理的对象,好方便invoke里面的操作
this.order = order;
//把真正的订单对象和动态代理关联起来
OrderApi orderApi = (OrderApi) Proxy.newProxyInstance(
order.getClass().getClassLoader(),
order.getClass().getInterfaces(),
this);
return orderApi;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//如果是调用setter方法就需要检查权限
if(method.getName().startsWith("set")){
//如果不是创建人,那就不能修改
if(order.getOrderUser()!=null
&& order.getOrderUser().equals(args[1])){
//可以操作
return method.invoke(order, args);
}else{
System.out.println("对不起,"+args[1]
+",您无权修改本订单中的数据");
}
}else{
//不是调用的setter方法就继续运行
return method.invoke(order, args);
}
return null;
}
}
要看明白上面的实现,需要熟悉Java反射的知识,这里就不去展开了。
(4)看看现在的客户端如何使用这个动态代理,示例代码如下:
public class Client {
public static void main(String[] args) {
//张三先登录系统创建了一个订单
Order order = new Order("设计模式",100,"张三");
//创建一个动态代理
DynamicProxy dynamicProxy = new DynamicProxy();
//然后把订单和动态代理关联起来
OrderApi orderApi = dynamicProxy.getProxyInterface(order);
//以下就需要使用被代理过的接口来操作了
//李四想要来修改,那就会报错
orderApi.setOrderNum(123, "李四");
//输出order
System.out.println("李四修改后订单记录没有变化:"+orderApi);
//张三修改就不会有问题
orderApi.setOrderNum(123, "张三");
//再次输出order
System.out.println("张三修改后,订单记录:"+orderApi);
}
}
运行结果如下:
对不起,李四,您无权修改本订单中的数据
李四修改后订单记录没有变化:
productName=设计模式,orderNum=100,orderUser=张三
张三修改后,订单记录:productName=设计模式,orderNum=123,orderUser=张三
运行的结果跟前面完全由自己实现的代理模式是一样的。
事实上,Java的动态代理还是实现AOP(面向方面编程)的一个重要手段,AOP的知识这里暂时不去讲述,大家先了解这一点就可以了。
11.3.4 代理模式的优缺点
代理模式在客户和被客户访问的对象之间,引入了一定程度的间接性,客户是直接使用代理,让代理来与被访问的对象进行交互。不同的代理类型,这种附加的间接性有不同的用途,也就是有不同的特点:
- 远程代理:隐藏了一个对象存在于不同的地址空间的事实,也即是客户通过远程代理去访问一个对象,根本就不关心这个对象在哪里,也不关心如何通过网络去访问到这个对象,从客户的角度来讲,它只是在使用代理对象而已。
- 虚代理:可以