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

代理模式也是生活中比较常见的一种模式,比如,现在火车站在城市中各处都会设置代售点,这就是一种代理模式。

代理模式的组成

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

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

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

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

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

代理模式的类图

模拟代售点火车售票

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

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框架中就使用了代理模式来管理事务,事务开启、回调、提交操作都是代理类完成的,我们只需要关心数据库操作即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值