您的位置:首页 > 其它

慢啃设计模式:代理模式Proxy

2015-05-08 17:45 519 查看
代理模式也是生活中比较常见的一种模式,比如,现在火车站在城市中各处都会设置代售点,这就是一种代理模式。


代理模式的组成

代理模式由三种角色构成:

抽象角色。通过接口或抽象类声明真实角色实现的业务方法。所谓抽象角色,实际上就是定义真实中角色所能完成的动作,但不做具体实现。

真实角色。实现抽象角色,实现真实角色所要实现的业务逻辑,供代理角色调用。真实角色需要实现抽象角色中定义的动作。

代理角色。实现抽象角色,是真实角色的代理,通过调用真实角色的业务逻辑方法来实现抽象方法,并附加自己的操作。代理角色可以通过直接调用真实角色的方法实现业务逻辑,自然也可以在此前后添加自己的操作代码。

以火车票售票为例,抽象角色即为售票处,它有售卖火车票的功能;真是角色就是火车站的售票处,它可以完成售票功能;代售点则为代理角色,它最终通过火车站的售票处完成售票功能,但又可以有自己的操作,比如手续费。


代理模式的类图




模拟代售点火车售票

首先我们创建一个售票接口,其扮演抽象角色:

package com.proxy.ticket;

/**
* 售票处
*/
public interface TicketOffice {

/**
* 售票
* @param count 售票张数
*/
public void sale(int count);

}


接下来创建火车站售票处,这个是真实角色,真的可以买票的地方:
package com.proxy.ticket;

/**
* 火车站售票处
*/
public class StationTicketOffice implements TicketOffice{

/* (non-Javadoc)
* @see com.proxy.ticket.TicketOffice#sale(int)
*/
public void sale(int count) {
System.out.println("卖出" + count + "张票。");
}

}


最后,我们可以开设火车票代售点了:

package com.proxy.ticket;

/**
* 火车票代售点
*/
public class ProxyStationTicketOffice implements TicketOffice{

// 与真实角色的联系纽带
private TicketOffice office;

/**
* 构造代售点
* @param office
*/
public ProxyStationTicketOffice (TicketOffice office){
this.office = office;
}

/* (non-Javadoc)
* @see com.proxy.ticket.TicketOffice#sale(int)
*/
public void sale(int count) {
// 提示不受理退票
System.out.println("代售点不受理退票");
// 售票功能由真实角色完成
this.office.sale(count);
// 收取手续费
System.out.println("收取手续费5元");
}

}


至此,我们就可以选择去火车站买票或者到代售点买票了。现在模拟一下买票:
package com.proxy.ticket;

/**
* 代理模式测试类
*/
public class TestTicketOffice {
public static void main(String[] args) {
// 这里是火车站售票处
TicketOffice station = new StationTicketOffice();
// 这是一家代售点
TicketOffice proxy = new ProxyStationTicketOffice (station);

// 去火车站买一张票
station.sale(1);
// 去代售点买一张票
proxy.sale(1);
}
}


执行结果:

卖出1张票。
代售点不受理退票
卖出1张票。
收取手续费5元


静态代理模式的局限性

实际上,上面这种通过实现接口或者继承抽象类的方式实现的代理模式,被称作静态代理模式。静态代理模式的所有代理类都在编译期就已经确定,并生成对应的class文件,并且一个代理类只为一个接口服务。这里举的例子是代购火车票,那如果连付款我们也要代理呢?按照静态代理模式这种思路,首先我们要创建一个付款接口作为抽象角色,然后再建一个银行付款类充当真实角色,那么代理角色可能就是支付宝。

这样下去,我们需要创建多少个代理类呢?


动态代理模式

既然有静态代理模式,自然有与之对应的动态代理模式。动态代理模式不需要开发者手工写各种代理类,它的代理类是由Java反射机制在程序运行时动态创建的。

要实现动态代理,我们需要使用JDK中java.lang.reflect包下的Proxy类和InvocationHandler接口,它们提供了动态代理的功能。Proxy的newProxyInstance方法可以创建一个代理对象,它需要三个参数:

ClassLoader loader:第一个参数是类加载器,用于定义代理类。

Class<?>[] interfaces:第二个参数是接口数组,在动态定义代理类的同时,会让代理类实现这些接口。

InvocationHandler h:第三个参数为回调句柄。

InvocationHandler接口下只有一个方法invoke需要重写,代理对象在调用其业务代码时,实际上就是执行这个invoke方法。它也有三个参数:

Object proxy:代理对象。

Method method:需要代理的方法

Object[] args:方法执行传递的参数

仍以前面的火车售票为例,不修改TicketOffice和StationTicketOffice类,来看看动态代理的代理类如何实现:

package com.proxy.ticket;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
* 代理工厂
*/
public class ProxyFactory implements InvocationHandler{

/**
* 保存原始对象(真实角色)
*/
private Object target;

/**
* 获取代理对象(代理角色)
* @param o
* @return
*/
public Object getProxy(Object o){
this.target = o;
Class<?> clazz = o.getClass();

// 创建代理对象实例
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}

/**
* 回调处理
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("开始代理");
// 业务仍由真实角色完成
Object result = method.invoke(target, args);
System.out.println("完成代理");

// 返回结果
return result;
}

}


当然,测试代码也会有一些改动:
package com.proxy.ticket;

/**
* 测试JDK动态代理
*/
public class TestJDKProxy {
public static void main(String[] args) {
// 创建代理工厂
ProxyFactory factory = new ProxyFactory();

// 这里是火车站售票处
TicketOffice ticket = new StationTicketOffice();
// 获取代理对象
TicketOffice t = (TicketOffice) factory.getProxy(ticket);
// 去买两张票
t.sale(2);
}
}


执行结果如下:
开始代理
卖出2张票。
完成代理


因为使用了动态代理,所以,我们也可以用这个代理类来处理付款代理了。
package com.proxy.ticket;

/**
* 付款
*/
public interface Payment {

/**
* 支付
*
* @param d
*/
public void pay(double d);

}

package com.proxy.ticket;

/**
* 银行付款
*/
public class BankPayment implements Payment{

public void pay(double d) {
System.out.println("本次支付" + d + "元");
}

}


无需对代理类做任何修改,更不需要添加新的代理类,测试代码如下:

package com.proxy.ticket;

/**
* 测试JDK动态代理
*/
public class TestJDKProxy {
public static void main(String[] args) {
// 创建代理工厂
ProxyFactory factory = new ProxyFactory();

// 这里是银行付款
Payment pay = new BankPayment();
// 付款代理
Payment payProxy = (Payment) factory.getProxy(pay);
// 支付15元
payProxy.pay(15.0);
}
}


执行结果:
开始代理
本次支付15.0元
完成代理


CGLIB实现动态代理

从前面的代码可以看出,无论是静态代理,还是动态代理,都需要先定义一个接口。这样就会造成普通的类无法对其生成代理类,对我们来说它也是有局限性的。不过好在我们可以使用cglib库来突破这个局限,对普通的java类动态的生成代理类。

cglib是针对类生成代理类的,它的原理是对指定对象的类生成一个子类,并覆盖其中的方法实现代理功能。由于采用的是继承机制,所以如果一个类是final的,cglib是无法对其进行代理的。下面是一个简单的cglib代理对象生成类:
package com.cglib.proxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
* cglib生成代理类
*
*/
public class CglibProxy implements MethodInterceptor{

// 原始对象
private Object target;

/**
* 生成代理对象
* @param o
* @return
*/
public Object getProxy(Object o){
this.target = o;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(o.getClass());
enhancer.setCallback(this);
return enhancer.create();
}

/**
* 代理回调
*/
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {

System.out.println("代理开始");
// 三种方式调用被代理对象的方法
method.invoke(target, args);
proxy.invokeSuper(obj, args);
proxy.invoke(target, args);
System.out.println("代理完成");
return null;
}

}


上面是一个较为通用的代理对象生成类,现在我们创建一个类,它不实现任何接口:

package com.cglib.proxy;

/**
* 不实现任何借口的普通类
*
*/
public class Normal {

public void execute(){
System.out.println("我是一个普通类");
}

}


再来写一个测试类:
package com.cglib.proxy;

public class TestCglibProxy {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
Normal normal = new Normal();
Normal proxyNormal = (Normal) proxy.getProxy(normal);
proxyNormal.execute();
}
}


执行结果:
代理开始
我是一个普通类
我是一个普通类
我是一个普通类
代理完成


可以看到,Normal类的对象已经被代理了。当我们调用execute时,代理类进行了干预。当然,如果Normal类有多个方法,那么它的任何一个方法在执行时,都会被代理。那么问题来了,如果我恰恰有一个类不希望被代理类干预呢?

cglib还提供了回调过滤器,它的作用就是控制哪个方法被哪个回调干预。现在我们需要修改一下getProxy方法:
/**
* 生成代理对象
* @param o
* @return
*/
public Object getProxy(Object o){
this.target = o;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(o.getClass());
// 赋予多个回调,NoOp.INSTANCE是一个没有任何操作的回调
enhancer.setCallbacks(new Callback[]{this, NoOp.INSTANCE});
// 指定回调过滤器
enhancer.setCallbackFilter(new CallbackFilter() {

/**
* 根据方法名控制当前操作受哪个回调干预
*
* @param method 当前执行的方法
* @return 前面设置的回调数组的下标
*/
public int accept(Method method) {
if("execute".equals(method.getName())){
return 0;
}
return 1;
}
});
return enhancer.create();
}


给Normal类添加一些其它方法,然后再次在测试类中调用,就会发现,只有调用execute方法时,代理对象才会起作用。


总结

设计模式能够让我们写出更为优雅的代码,如果不使用代理模式,最原始的实现方式恐怕就是去修改真实角色的方法,追加一些操作。但因为代理模式的存在,我们可以不修改原来的类,通过一个代理类,来管理其他类。比如我们要实现日志功能,就可以添加一个日志代理类,将所有的类都代理起来,在这些类的方法执行时,记录下类名、方法名、参数等信息。实际上Spring框架中就使用了代理模式来管理事务,事务开启、回调、提交操作都是代理类完成的,我们只需要关心数据库操作即可。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: