一.介绍
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口
二.UML类图
三.代理模式分类
Java中的代理按照代理类生成时机不同分为静态代理和动态代理,静态代理的代理类在编译器就生成,而动态代理的代理类在Java运行时动态生成。动态代理又分为JDK代理和CGLib代理。
四.静态代理
业务代码
/**
* 静态代理
*/
public interface Pay {
void pay();
}
//真实类
class Alipay implements Pay {
@Override
public void pay() {
System.out.println("支付宝支付");
}
}
//代理类
class AlipayProxy implements Pay{
//组合真实对象
private final Alipay alipay = new Alipay();
@Override
public void pay() {
long startTime = System.currentTimeMillis();
alipay.pay();
System.out.println("执行了" + (System.currentTimeMillis()-startTime) + "毫秒"); //支付宝支付 执行了0毫秒
}
}
测试代码
public class Client {
public static void main(String[] args) {
new AlipayProxy().pay();
}
}
五.静态代理的优缺点
- 优点
- 符合开闭原则
- 功能增强无需改动原业务代码(解耦)
- 缺点
- 一个具体类就要产生一个代理类,可能会造成类爆炸
六.动态代理
为了弥补静态代理的缺点,引入了动态代理
1.JDK动态代理(利用Java提供的代理机制)
业务代码
/**
* JDK动态代理
*/
public interface Pay {
void pay();
}
//真实类
class Alipay implements Pay {
@Override
public void pay() {
System.out.println("支付宝支付");
}
}
class PayProxy {
//组合真实对象
private Pay pay;
public PayProxy(Pay pay) {
this.pay = pay;
}
public Pay getProxy() {
return (Pay) Proxy.newProxyInstance(getClass().getClassLoader(), pay.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = method.invoke(pay, args);
System.out.println("执行了" + (System.currentTimeMillis() - startTime) + "毫秒");
return result;
}
});
}
}
测试代码
public class Client {
public static void main(String[] args) {
PayProxy payProxy = new PayProxy(new Alipay());
Pay pay = payProxy.getProxy();
pay.pay(); //支付宝支付 执行了0毫秒
}
}
我们通过arthas工具进行反编译,可以找到真正的代理类$Proxy0
//代理对象
public final class $Proxy0 extends Proxy implements Pay {
private static Method m3;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
// 通过反射获取名叫pay的menthod
m3 = Class.forName("com.designpattern.structure.proxy.v2.Pay").getMethod("pay", new Class[0]);
return;
}
public final void pay() {
// h是invocationHandler对象
this.h.invoke(this, m3, null);
return;
}
}
总结执行流程如下
- 测试代码里执行了pay.pay()
- 根据多态的特性,执行的是代理类($Proxy0)中的pay方法
- 代理类($Proxy0)中的pay方法中执行了invocationHandler对象的invoke方法
- invocationHandler对象的invoke方法就是业务代码中传入的匿名内部类中重写的invoke方法
- 在重写的invoke方法中通过反射调用真实对象alipay的pay方法
2.CGLib动态代理
JDK动态代理要求必须定义接口,如果没有定义接口,就可以使用CGLib动态代理,CGLib为JDK的动态代理提供了很好的补充
首先引入cglib-3.3.0.jar与asm-9.0.jar
业务代码
//真实对象
class Alipay implements Pay {
@Override
public void pay() {
System.out.println("支付宝支付");
}
}
class AlipayProxy implements MethodInterceptor {
//组合真实对象
private Alipay alipay = new Alipay();
public Alipay getProxy(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Alipay.class);
//设置回调函数
enhancer.setCallback(this);
//返回代理对象
return (Alipay) enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = method.invoke(alipay, args);
System.out.println("执行了" + (System.currentTimeMillis() - startTime) + "毫秒");
return result;
}
}
测试代码
public class Client {
public static void main(String[] args) {
Alipay proxy = new AlipayProxy().getProxy();
proxy.pay(); //支付宝支付 执行了0毫秒
}
}
七.JDK代理与CGLIB代理对比
- JDK代理要求必须定义接口,CGLib不用
- CGLib的原理是动态生成被代理类的子类,所以不能对final修饰的类或者方法进行代理
- CGLib代理速度>JDK代理速度的场景:JDK1.6之前、JDK1.6与JDK1.7进行大量调用,其余场景JDK代理速度更快(因此在有接口的情况下推荐使用JDK动态代理)
八.代理模式的优缺点
- 优点
- 保护真实对象,使用代理对象与客户端交互
- 符合开闭原则
- 客户端与真实对象之间解耦
- 缺点
- 代理类的创建,增加了系统复杂度
九.通用的动态代理类(拓展)
上文提到静态代理是一个具体类产生一个代理类,可能会造成类爆炸,我们现在反观动态代理则是一个接口产生一个代理类,也可能会造成类爆炸,所以这里给出一个较为通用的实现
业务代码
//记录执行的时间的通用代理类
public class TimeRecordProxy<T> {
private final T target;
public TimeRecordProxy(T target) {
this.target = target;
}
@SuppressWarnings("unchecked")
public T getProxy() {
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this::invoke);
}
private Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
long startTime = System.currentTimeMillis();
Object result = method.invoke(target, args);
System.out.println("执行了" + (System.currentTimeMillis()-startTime) + "毫秒");
return result;
}
}
测试代码
public class Client {
public static void main(String[] args) {
TimeRecordProxy<Pay> timeRecordProxy = new TimeRecordProxy<>(new Alipay());
timeRecordProxy.getProxy().pay(); //支付宝支付 执行了0毫秒
}
}