设计模式-代理模式

几种代理模式

1、虚拟代理

虚拟代理为创建开销大的对象提供代理服务,真正的对象在创建前和创建中时,由虚拟代理来扮演替身。
如:Android的在线图片加载类

2、动态代理

运行时动态的创建代理类对象,并将方法调用转发到指定类(方法的调用也是动态的)。

3、保护代理:

与动态代理搭配使用

防火墙代理

这种防火墙通过一种代理(Proxy)技术参与到一个TCP连接的全过程。从内部发出的数据包经过这样的防火墙处理后,就好像是源于防火墙外部网卡一样,从而可以达到隐藏内部网结构的作用。这种类型的防火墙被网络安全专家和媒体公认为是最安全的防火墙。

缓存代理

由一个代理服务器下载的页面存储。一个代理服务器为多个用户提供一条通道。缓冲的代理允许一个代理服务器减少对同一个网站的同样页面的请求次数。一旦代理服务器的一个用户请求了某页,代理服务器就保存该页以服务于它的其他用户的同样请求。

智能引用代理

智能代理(intelligentagent)是定期地收集信息或执行服务的程序,它不需要人工干预,具有高度智能性和自主学习性,可以根据用户定义的准则,主动地通过智能化代理服务器为用户搜集最感兴趣的信息,然后利用代理通信协议把加工过的信息按时推送给用户,并能推测出用户的意图,自主制订、调整和执行工作计划。

同步代理

用于多线程之间同步访问对象

写入时复制代理

用于保留某些数据的原始副本的一种技术。在写入操作修改数据时,会复制数据的原始副本到其他位置。
写入时复制(Copy-on-write)是一个被使用在程序设计领域的最佳化策略。其基础的观念是,如果有多个呼叫者(callers)同时要求相同资源,他们会共同取得相同的指标指向相同的资源,直到某个呼叫者(caller)尝试修改资源时,系统才会真正复制一个副本(private copy)给该呼叫者,以避免被修改的资源被直接察觉到,这过程对其他的呼叫只都是通透的(transparently)。此作法主要的优点是如果呼叫者并没有修改该资源,就不会有副本(private copy)被建立。

代理模式

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

介绍

意图:

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

关键代码:

实现与被代理类组合。

应用实例:

1、Windows 里面的快捷方式。
2、买火车票不一定在火车站买,也可以去代售点。 3、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。
4、spring aop。

优点:

1、职责清晰。
2、高扩展性。
3、智能化。

缺点:

1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

注意事项:

1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

实现

创建一个 Image 接口和实现了 Image 接口的实体类。
ProxyImage 是一个代理类,减少 RealImage 对象加载的内存占用。
ProxyPatternDemo,演示类使用 ProxyImage 来获取要加载的 Image对象,并按照需求进行显示。
Image text

1、创建一个接口Image。

1
2
3
public interface Image {
void display();
}

2、创建实现接口的实体类RealImage以及实现接口的代理类ProxyImage。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class RealImage implements Image {

private String fileName;

public RealImage(String fileName){
this.fileName = fileName;
loadFromDisk(fileName);
}

@Override
public void display() {
System.out.println("Displaying " + fileName);
}

private void loadFromDisk(String fileName){
System.out.println("Loading " + fileName);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ProxyImage implements Image{

private RealImage realImage;
private String fileName;

public ProxyImage(String fileName){
this.fileName = fileName;
}

@Override
public void display() {
if(realImage == null){
realImage = new RealImage(fileName);
}
realImage.display();
}
}

3、当被请求时,使用 ProxyImage 来获取 RealImage 类的对象。

1
2
3
4
5
6
7
8
9
10
11
12
public class ProxyPatternDemo {

public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg");

// 图像将从磁盘加载
image.display();
System.out.println("");
// 图像不需要从磁盘加载
image.display();
}
}

4、输出

1
2
3
4
Loading test_10mb.jpg
Displaying test_10mb.jpg

Displaying test_10mb.jpg

JDK 自带的动态代理,针对有接口情况

java.lang.reflect.Proxy:生成动态代理类和对象;
java.lang.reflect.InvocationHandler(处理器接口):可以通过invoke方法实现对真实角色的代理访问。
每次通过 Proxy 生成的代理类对象都要指定对应的处理器对象。


1、接口:Subject.java

1
2
3
4
5
public interface Subject {
public int sellBooks();

public String speak();
}

2、真实对象:RealSubject.java

1
2
3
4
5
6
7
8
9
10
11
12
13
public class RealSubject implements Subject{
@Override
public int sellBooks() {
System.out.println("卖书");
return 1 ;
}

@Override
public String speak() {
System.out.println("说话");
return "张三";
}
}

3、处理器对象:MyInvocationHandler.java

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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
* 定义一个处理器
* @author gnehcgnaw
* @date 2018/11/5 19:26
*/
public class MyInvocationHandler implements InvocationHandler {
/**
* 因为需要处理真实角色,所以要把真实角色传进来
*/
Subject realSubject ;

public MyInvocationHandler(Subject realSubject) {
this.realSubject = realSubject;
}

/**
*
* @param proxy 代理类
* @param method 正在调用的方法
* @param args 方法的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用代理类");
if(method.getName().equals("sellBooks")){
int invoke = (int)method.invoke(realSubject, args);
System.out.println("调用的是卖书的方法");
return invoke ;
}else {
String string = (String) method.invoke(realSubject,args) ;
System.out.println("调用的是说话的方法");
return string ;
}
}
}

4、调用端:Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.lang.reflect.Proxy;

/**
* 调用类
* @author gnehcgnaw
* @date 2018/11/7 20:26
*/
public class Client {
public static void main(String[] args) {
//真实对象
Subject realSubject = new RealSubject();

MyInvocationHandler myInvocationHandler = new MyInvocationHandler(realSubject);
//代理对象
Subject proxyClass = (Subject) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Subject.class}, myInvocationHandler);

proxyClass.sellBooks();

proxyClass.speak();
}
}

动态代理是针对代理的类, 动态生成一个子类, 然后子类覆盖代理类中的方法, 如果是private或是final类修饰的方法,则不会被重写。

Cglib动态代理,针对没有接口的情况

Cglib动态代理动态代理是针对代理的类, 动态生成一个子类, 然后子类覆盖代理类中的方法, 如果是private或是final类修饰的方法,则不会被重写。
CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。

1、需要代理的类Engineer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Engineer {
// 可以被代理
public void eat() {
System.out.println("工程师正在吃饭");
}

// final 方法不会被生成的字类覆盖
public final void work() {
System.out.println("工程师正在工作");
}

// private 方法不会被生成的字类覆盖
private void play() {
System.out.println("this engineer is playing game");
}
}

2、CGLIB 代理类:

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 CglibProxy implements MethodInterceptor {
private Object target;

public CglibProxy(Object target) {
this.target = target;
}

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("### before invocation");
Object result = method.invoke(target, objects);
System.out.println("### end invocation");
return result;
}

public static Object getProxy(Object target) {
Enhancer enhancer = new Enhancer();
// 设置需要代理的对象
enhancer.setSuperclass(target.getClass());
// 设置代理人
enhancer.setCallback(new CglibProxy(target));
return enhancer.create();
}
}

3、测试类:

1
2
3
4
5
6
7
8
public class CglibMainTest {
public static void main(String[] args) {
// 生成 Cglib 代理类
Engineer engineerProxy = (Engineer) CglibProxy.getProxy(new Engineer());
// 调用相关方法
engineerProxy.eat();
}
}

4、输出

1
2
3
###   before invocation
工程师正在吃饭
### end invocation

参考:http://www.runoob.com/design-pattern/proxy-pattern.html