代理模式

代理模式

我们买房子或者租房子,我们可以自己去找房源,也可以通过房屋中介来完成。

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

动机:对一个对象进行访问控制的一个原因是为了只有在我们确实需要这个对象时才对它进行创建和初始化。

参与者

  • Proxy

    保存一个引用使得代理可以访问实体。如RealSubject和Subject的接口相同,Proxy会引用Subject。

    提供一个与Subject的接口相同的接口,这样代理就可以来替代实体。

    控制对实体的存取,并可能负责创建和删除它。

    其他功能依赖于代理的类型:

    • Remote Proxy负责对请求及其参数进行编码,并向不同地址空间中的实体发送已编码的请求。
    • Virtual Proxy可以缓存实体的附加信息,以便延迟对它的访问。
    • Protection Proxy检查调用者是否具有实现一个请求所必须的访问权限
  • Subject

    定义RealSubject和Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy。

  • Real Subject

    定义Proxy 所代表的实体。

分类

按照代理创建的时期来进行分类的话,可以分为两种:静态代理、动态代理。

静态代理

Proxy.h

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
#ifndef _PROXY_H
#define _PROXY_H

class Subject
{
public:
Subject() {}
virtual ~Subject(){};
virtual void request() = 0;
};

class RealSubject: public Subject
{
public:
RealSubject();
virtual ~RealSubject();
virtual void request();
};

class Proxy: public Subject
{
public:
Proxy();
virtual ~Proxy();
virtual void request();

private:
RealSubject* realSubject;
};

#endif

Proxy.cpp

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
#include "Proxy.h"
#include <iostream>

using namespace std;

RealSubject::RealSubject()
{
cout << "RealSubject constructor" << endl;
}

RealSubject::~RealSubject()
{
cout << "RealSubject destructor" << endl;
}

void RealSubject::request()
{
cout << "RealSubject request" << endl;
}

Proxy::Proxy(): realSubject(NULL)
{
cout << "Proxy constructor" << endl;
}

Proxy::~Proxy()
{
cout << "Proxy destructor" << endl;
delete realSubject;
realSubject = NULL;
}

void Proxy::request()
{
cout << "Proxy request" << endl;
if (!realSubject) {
realSubject = new RealSubject();
}
realSubject->request();
}

Main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "Proxy.h"
#include <iostream>

using namespace std;

int main()
{
Subject* proxy = new Proxy();
proxy->request();

delete proxy;
proxy = NULL;

return 0;
}

Output

1
2
3
4
5
6
Proxy constructor
Proxy request
RealSubject constructor
RealSubject request
Proxy destructor
RealSubject destructor

Java example

1
2
3
4
public interface IShape {
void draw();
double area();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Circle implements IShape {
private int radius;

Circle(int r) {
radius = r;
}

@Override
public void draw() {
System.out.println("Draw Circle");
}

@Override
public double area(){
return Math.PI * radius * radius;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ShapeProxy implements IShape {
private IShape shape;
public ShapeProxy(IShape shape)
{
this.shape = shape;
}

@Override
public void draw() {
shape.draw();
}

@Override
public double area() {
return shape.area();
}
}

动态代理

静态代理会随着业务庞大导致方法数量越来越多,代理类的代码量就会变得十分庞大。所以引入了动态代理来解决此类问题。

  • Java 1.3 supports the creation of dynamic proxy classes and instances
  • A dynamic proxy class is a class that implements a list of interfaces specified at runtime when the class is created
  • A proxy interface is an interface that is implemented by a proxy class
  • A proxy instance is an instance of a proxy class
  • Each proxy instance has an associated invocation handler object, which implements the interface InvocationHandler
  • A method invocation on a proxy instance through one of its proxy interfaces will be dispatched to the invoke() method of the instance’s invocation handler
  • Proxy classes are created using the new java.lang.reflect.Proxy class
  • Proxy classes are public, final, non-abstract subclasses of java.lang.reflect.Proxy
  • The unqualified name of a proxy class is unspecified. The space of class names that begin with the string “$Proxy” should be, however, reserved for proxy classes.
  • A proxy class implements exactly the interfaces specified at its creation l Since a proxy class implements all of the interfaces specified at its creation, invoking getInterfaces() on its Class object will return an array containing the same list of interfaces (in the order specified at its creation)
  • Each proxy class has one public constructor that takes one argument, an implementation of the interface InvocationHandler, to set the invocation handler for a proxy instance
  • Rather than having to use the reflection API to access the public constructor, a proxy instance can be also be created by calling the Proxy.newInstance() method, which combines the actions of calling Proxy.getProxyClass() with invoking the constructor with an invocation handler
1
2
3
4
5
6
7
8
9
10
11
12
public class ShapeHandler implements InvocationHandler {
private IShape shape;

public ShapeHandler(IShape shape){
this.shape = shape;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(shape, args);
}
}
1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) {
IShape circle = new Circle(5);
ClassLoader cl = IShape.class.getClassLoader();
IShape shape = (IShape) Proxy.newProxyInstance(cl, new Class[]{IShape.class}, new ShapeHandler(circle));
shape.draw();
System.out.println(shape.area());
}

JDKProxy vs. CGLIB

Java Proxy
1
2
3
4
5
6
7
8
9
10
11
public static Object newProxyInstance(ClassLoader loader,
Class[] interfaces,
InvocationHandler ih)
throws IllegalArgumentException

public static InvocationHandler getInvocationHandler
(Object proxy)
throws IllegalArgumentException

public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable

关键方法

1
2
3
4
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException

该方法会根据指定的参数动态创建代理对象,三个参数的意义如下:

  1. loader 指定代理对象的类加载器
  2. interfaces 代理对象需要实现的接口,可以同时指定多个接口
  3. handler 方法调用的实际处理者,代理对象的方法调用都会转发到这里。

newProxyInstance() 会返回一个实现了指定接口的代理对象,对该对象的所有方法调用都会转发给InvocationHandler.invoke()方法。

Java动态代理是基于接口的,如果对象没有实现接口的话我们就要使用Cglib了。

CGLOB(Code Generation Library)

CGLIB是基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。

CGLIB通过继承方式实现代理。

通过CGLIB代理实现如下:

  1. 首先要实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
  2. 在需要使用类的时候,通过CGLIB动态代理获取代理对象。

JDK原生动态代理是Java原生支持的,不需要任何外部依赖,但是只能基于接口进行代理;

CGLIB通过继承的方法进行代理,无论目标对象有没有实现接口都可以代理,但是无法处理final的情况。

应用场景

如果已有的方法在使用的时候需要对原有的方法进行改进,此时有两种方法:

  1. 修改原有的方法来适应。这样违反了“对扩展开放,对修改关闭”的原则。
  2. 就是采用一个代理类调用原有的方法,且对产生的结果进行控制。这种方法就是代理模式。

使用代理模式,可以将功能划分的更加清晰,有助于后期维护!

常见情况

远程代理(Remote Proxy)

为一个对象在不同的地址空间提供局部代表。使得客户端程序可以访问在远程主机上的对象

可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在

虚代理(Virtual Proxy)

根据需要创建开销很大的对象。

通常,以下两种情况下可以考虑使用虚拟代理:

  1. 由于对象本身的复杂性或者网络等原因导致一个对象需要较长的加载时间,此时可以用一个加载时间相对较短的代理对象来代表真是对象。
  2. 当一个对象的加载十分耗费系统资源的时候,也非常适合使用虚拟代理。
保护代理(Protection Proxy)

控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。

智能指引(Smart Reference)

取代了简单的指针,它在访问对象时执行一些附加操作。它的典型用途包括:

  • 对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它。
  • 当第一次引用一个持久对象时,将它装入内存。
  • 在访问一个控制对象前,检查是否已经锁定了它,以确保其他对象不能改变它。
缓冲代理(Cache Proxy)

缓冲代理为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,从而可以避免某些方法的重复执行,优化系统性能。

Spring AOP

动态代理是Spring AOP的实现方法。

Reference

https://www.zhihu.com/question/20794107/answer/23330381

http://ifeve.com/jdk%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E4%BB%A3%E7%90%86%E4%B8%8Ecglib%E4%BB%A3%E7%90%86%E5%8E%9F%E7%90%86%E6%8E%A2%E7%A9%B6/

More than your eyes can see