Java设计模式之代理模式

定义

为其他对象提供一种代理便以控制这个对象的访问。

介绍

代理模式(Proxy)属于结构型模式,也叫委托模式。

代理模式的特征是代理类委托类实现同样的接口,代理类主要负责为委托类预处理消息,过滤消息,把消息转发给委托类,以及事后消息处理。

代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

日常生活中,代购,买房、租房都是属于常见的代理模式。

UML

角色说明

  • Subject (抽象主题类):接口或者抽象类,真实声明主题和代理的共同接口方法。
  • RealSubject(真实主题类):被代理类或者委托类,定义代理所表示的真实对象,负责具体业务逻辑的执行,客户端可以通过代理类间接调用真实主题类的方法。
  • Proxy(代理类):委托类,持有对真实主题类的引用,在其所实现接口的方法中调用真实主题类中相应的接口方法执行。
  • Client(客户端类):使用代理模式的地方

模拟实现

Subject类定义RealSubject和Proxy的公用接口。

1
2
3
4
5
6
public interface Subject {
/**
* 请求接口
*/
void request();
}

RealSubject类定义Proxy所代表的真实请求

1
2
3
4
5
6
7
8
9
public class RealSubject implements Subject {
/**
* 请求接口
*/
@Override
public void request() {
System.out.println("我是真实的请求");
}
}

Proxy类:保存了一个引用使得代理可以访问实体,并提供一个与Subject的接口相同的接口,这种代理就可以用来替代实体;

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Proxy implements Subject {
// 需要注入bean
private RealSubject realSubject;

/**
* 请求接口
*/
@Override
public void request() {
// 调用真实的请求
realSubject.request();
}
}

客户端调用

1
2
3
4
5
@Test
public void test1(){
Proxy proxy = new Proxy();
proxy.request();
}

案例实现

背景:天朝的码农小明需要一台全新的顶配Thinkpad T480笔记本
问题:国行价格太高,无法定制配置
解决:通过代购从米国直接代购空运回国

代购(代理对象)替小明(真实对象)去购买笔记本

创建抽象主题类 Trade

1
2
3
4
5
6
7
8
public interface Trade {
/**
* 购买
*
* @param name 购买的商品名字
*/
void buy(String name);
}

创建真实主题类

1
2
3
4
5
6
7
8
9
10
11
public class XiaoMing implements Trade {
/**
* 购买
*
* @param name 购买的商品名字
*/
@Override
public void buy(String name) {
System.out.println("我需要代购" + name);
}
}

创建代理类,即海外代购中介

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Proxy implements Trade {
private XiaoMing xiaoMing;

// 构造函数创建真实的对象实例
public Proxy(XiaoMing xiaoMing) {
this.xiaoMing = xiaoMing;
}

/**
* 购买
*
* @param name 购买的商品名字
*/
@Override
public void buy(String name) {
System.out.println("中介: 大家好,我是代购,很高兴为你服务。");
// 调用真实的对象方法进行代理购买商品
xiaoMing.buy(name);
//代理对象的操作
this.wrapBuy();
}

private void wrapBuy(){
System.out.println("对商品进行一次包装");
}
}

代购测试

1
2
3
4
5
6
7
8
9
10
public class ProxyTest {

@Test
public void test1() {
// 创建代购类,并讲需要代购的人作为构造函数传递
Proxy proxy = new Proxy(new XiaoMing());
// 调用代购的方法
proxy.buy("笔记本ThinkPad T480");
}
}

输出结果

1
2
3
中介: 大家好,我是代购,很高兴为你服务。
我需要代购笔记本ThinkPad T480
对商品进行一次包装

通过上面简单的代码示例,我们可以轻松理解什么是代理模式,通过代理我们可以轻松从国外购买想要的商品。

在购买商品物流过程中,如果代购害怕商品损坏,会对原有的商品在进行一次深层次的包装,避免物流暴力运输损坏商品。这个其实就是代理模式中可以增强类原本的功能。

上面简单的实现属于静态代理模式,即在程序运行前,代理类的.class文件就已经存在了。

代理模式优缺点

优点

  • 降低系统的耦合度
  • 代理对象作为中介,起到保护目标对象的作用

缺点

  • 客户端和目标端之间增加代理层,请求速度会变慢
  • 静态代理模式增加系统复杂度

应用场景

应用场景 具体描述 目的
远程代理 为一个对象在不同地址空间提供局部代理 - 隐藏一个对象存在于不同地址空间的事实 - 远程机器有更好的性能可以出来客户端的请求
虚拟代理 通过一个小的对象代理一个开销大的对象 减少系统开销
保护代理 控制目标对象访问,给不同用户提供不同的权限 控制对真实对象的访问权限
智能应用代理 在访问对象时附加额外操作 在不影响对象类的情况下,在访问对象时进行更多的操作
其他 防火墙代理,缓存代理

动态代理模式

实现原理

所谓动态代理,就是动态代理类的.class文件是在程序运行时候由JAVA反射机制动态生成,无需提前编译。

动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。

优点

只需要一个动态代理类就可以解决创建多个静态代理的问题,避免发重复、多余代码,增强灵活性。

设计动态代理类(DynamicProxy)时,不需要显式实现与目标对象类(RealSubject)相同的接口,而是将这种实现推迟到程序运行时由 JVM来实现

在使用时(调用目标对象方法时)才会动态创建动态代理类 & 实例,不需要事先实例化

缺点

效率低
相比静态代理中 直接调用目标对象方法,动态代理则需要先通过Java反射机制 从而 间接调用目标对象方法

应用场景局限
因为 Java 的单继承特性(每个代理类都继承了 Proxy 类),即只能针对接口 创建 代理类,不能针对类 创建代理类

即只能动态代理 实现了接口的类

应用场景

  • 基于静态代理应用场景下,需要代理对象数量较多的情况下使用动态代理
  • AOP 领域
    • 定义:即 Aspect Oriented Programming = 面向切面编程,是OOP的延续、函数式编程的一种衍生范型
    • 作用:通过预编译方式和运行期动态代理实现程序功能的统一维护。
    • 优点:降低业务逻辑各部分之间的耦合度 、 提高程序的可重用性 & 提高了开发的效率
    • 具体应用场景:日志记录性能统计安全控制异常处理

从JDK源码分析

Java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类

Interface InvocationHandler

该接口仅定义1个方法

1
2
3
4
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}

在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,如上例中的request(),args为该方法的参数数组。这个抽象方法在代理类中动态实现。

Proxy 动态代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
protected Proxy(InvocationHandler h) {
doNewInstanceCheck();
this.h = h;
}

public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
{
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}

return getProxyClass0(loader, intfs);
}


public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
if (h == null) {
throw new NullPointerException();
}

final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}

/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);

/*
* Invoke its constructor with the designated invocation handler.
*/
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
// create proxy instance with doPrivilege as the proxy class may
// implement non-public interfaces that requires a special permission
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
return newInstance(cons, ih);
}
});
} else {
return newInstance(cons, ih);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
}
}

protected Proxy(InvocationHandler h):构造函数,用于给内部的h赋值。

static Class getProxyClass (ClassLoaderloader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。

static Object newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)

所谓DynamicProxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然,这个DynamicProxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。

在使用动态代理类时,我们必须实现InvocationHandler接口

通过这种方式,被代理的对象(RealSubject)可以在运行时动态改变,需要控制的接口(Subject接口)可以在运行时改变,控制的方式(DynamicSubject类)也可以动态改变,从而实现了非常灵活的动态代理关系

动态代理步骤

1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法
2.创建被代理的类以及接口
3.通过Proxy的静态方法
newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理
4.通过代理调用方法

场景

背景:小明是JAVA服务端开发需要一台全新的顶配Thinkpad T480笔记本,小华是IOS开发者需要MAC PRO顶配
问题:国行价格太高
解决:通过代购从米国直接代购空运回国

即1个代购(动态代理对象)同时 代替 小成 & 小何(目标对象) 去买Mac(间接访问的操作)
该代购是代购任何商品 = 什么人有什么需求就会去代购任何东西(动态代理)

代码实现

  1. 声明调用处理类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    /**
    * @Package: com.seed.proxy3
    * @Description: 1. 生成 动态代理对象
    * 2. 指定 代理对象运行目标对象方法时需要完成的 具体任务
    * 注:需实现InvocationHandler接口 = 调用处理器 接口
    * 所以称为 调用处理器类
    * @Author: hanfeng
    * @CreateDate: 2018-9-14 14:55
    * @Version: 1.0.0
    **/
    public class DynamicProxy implements InvocationHandler {
    // 声明代理对象
    // 作用:绑定关系,即关联到哪个接口(与具体的实现类绑定)的哪些方法将被调用时,执行invoke()
    private Object proxyObject;

    public Object newProxyInstance(Object proxyObject) {
    this.proxyObject = proxyObject;
    return Proxy.newProxyInstance(proxyObject.getClass().getClassLoader(),
    proxyObject.getClass().getInterfaces(), this);
    // Proxy类 = 动态代理类的主类
    // Proxy.newProxyInstance()作用:根据指定的类装载器、一组接口 & 调用处理器 生成动态代理类实例,并最终返回
    // 参数说明:
    // 参数1:指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
    // 参数2:指定目标对象的实现接口
    // 即要给目标对象提供一组什么接口。若提供了一组接口给它,那么该代理对象就默认实现了该接口,这样就能调用这组接口中的方法
    // 参数3:指定InvocationHandler对象。即动态代理对象在调用方法时,会关联到哪个InvocationHandler对象

    }

    /**
    * 复写InvocationHandler接口的invoke()
    * 动态代理对象调用目标对象的任何方法前,都会调用调用处理器类的invoke()
    *
    * @param proxy 动态代理对象(即哪个动态代理对象调用了method()
    * @param method 目标对象被调用的方法
    * @param args 指定被调用方法的参数
    * @return
    * @throws Throwable
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    System.out.println("代购去商城挑选商品啦");
    Object result = null;
    // 通过Java反射机制调用目标对象方法
    result = method.invoke(proxyObject, args);
    return result;
    }
    }
  2. 声明目标对象抽象接口类

    1
    2
    3
    4
    5
    6
    public interface Subject {
    /**
    * 购买商品
    */
    void buy(String name);
    }
  3. 声明目标对象 xiaoming

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class XiaoMing implements Subject {
    /**
    * 购买商品
    *
    * @param name
    */
    @Override
    public void buy(String name) {
    System.out.println("我是小明,我需要购买" + name);
    }
    }
  4. 声明目标对象 xiaohua

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class XiaoHua implements  Subject {
    /**
    * 购买商品
    *
    * @param name
    */
    @Override
    public void buy(String name) {
    System.out.println("我是小华,我需要购买" + name);
    }
    }
  5. 通过动态代理对象,调用目标对象的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class DynamicProxyTest {
    @Test
    public void test1() {
    // 创建调用处理器类对象
    DynamicProxy dynamicProxy = new DynamicProxy();

    //创建目标对象 小明
    XiaoMing xiaoMing = new XiaoMing();

    // 创建动态代理类 & 对象:通过调用处理器类对象newProxyInstance()
    // 实际上是调用了invoke(),再通过invoke()里的反射机制调用目标对象的方法
    Subject xiaoming_subject = (Subject) dynamicProxy.newProxyInstance(xiaoMing);

    //通过调用动态代理对象方法从而调用目标对象方法
    xiaoming_subject.buy("Thinkpad T480");

    XiaoHua xiaoHua = new XiaoHua();
    // 创建动态代理类 & 对象:通过调用处理器类对象newProxyInstance()
    Subject xiaohua_subject = (Subject) dynamicProxy.newProxyInstance(xiaoHua);

    //通过调用动态代理对象方法从而调用目标对象方法
    xiaohua_subject.buy("Mac Pro");
    }
    }
  6. 测试结果

    1
    2
    3
    4
    代购去商城挑选商品啦
    我是小明,我需要购买Thinkpad T480
    代购去商城挑选商品啦
    我是小华,我需要购买Mac Pro

本文标题:Java设计模式之代理模式

文章作者:hanfeng

发布时间:2018年09月13日 - 21:09

最后更新:2018年09月14日 - 15:09

原始链接:http://blog.zhujunwang.cn/2018/09/13/proxy-mode-of-java-design-pattern/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。