代理模式

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用

Posted by 高明 on 2021-01-17

代理模式

代理模式给某一个对象提供一下代理对象,并由代理对象控制对原对象的引用。在代理模式中(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式数据结构型模式。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

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

主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

何时使用:想在访问一个类时做一些控制。

如何解决:增加中间层。

关键代码:实现与被代理类组合。

应用实例: 1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop

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

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

使用场景:按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。

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

为什么要用代理模式

中介隔离作用

在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口

开闭原则

代理类出了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。

代理类主要负责为委托类预处理消息,过滤消息,把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不实现服务,而是通过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。

例如:为项目加入缓存,日志等功能,可以使用代理类来完成,而没有必要打开已经封装好的委托类

静态代理

静态代理是由程序员创建或特定工具自动生成源代码,再对其编译,在程序运行之前,代理类就已经被创建了

1
2
3
public interface Image {
void display();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName);
}
private void loadFromDisk(String fileName) {
System.out.println("Loading " + fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();
}
}
1
2
3
4
5
6
7
8
9
10
public class ProxyPatternDemo {
public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg");
// 图像将从磁盘加载
image.display();
System.out.println("");
// 图像不需要从磁盘加载
image.display();
}
}

1
2
3
public interface Image {
void display();
}
1
2
3
4
5
6
7
8
9
10
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class ProxyImage implements Image {
private RealImage realImage;
public ProxyImage(RealImage realImage) {
this.realImage = realImage;
}
@Override
public void display() {
System.out.println("代理前doSomething");
realImage.display();
System.out.println("代理后doSomething");
}
}

静态代理总结

优点:可以做到在符合开闭原则的情况下,对目标对象进行功能扩展

缺点:为每一个服务都得创建一个代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改

动态代理

动态代理是程序运行时通过反射机制动态创建的

在动态代理中,我们不再需要手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK在运行时为我们动态来创建

1
2
3
public interface Image {
void display();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class RealImage implements Image {

private String fileName;

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

@Override
public void display() {
System.out.println("Displaying " + fileName);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DynamicProxyHandle implements InvocationHandler {

private Object object;

public DynamicProxyHandle(Object object) {
this.object = object;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理前doSomething");
Object result = method.invoke(object, args);
System.out.println("代理后面doSomething");
return result;
}
}
1
2
3
4
5
6
7
public class DynamicProxyTest {
public static void main(String[] args) {
Image realImage = new RealImage("http://www.baidu.com");
Image proxyRealImage = (Image) Proxy.newProxyInstance(Image.class.getClassLoader(), new Class[]{Image.class}, new DynamicProxyHandle(realImage));
proxyRealImage.display();
}
}

注意

Proxy.newProxyInstance()方法接收三个参数

  • *ClassLoader loader:*指定当前目标对象使用的类加载器,获取加载器的方法是固定的
  • *Class<?>[] interfaces:*指定目标对象实现的接口的类型,使用泛型方式确认类型
  • *InvocationHandler:*指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法

总结

虽然相对于静态代理,动态代理大大减少了开发量,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小遗憾,它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy

Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。

有很多理由,人们可以否定对class代理的必要性,但是同样有一些理由,相信支持class动态代理会更好。

接口和类的划分,本就不是很明显,只是到了Java中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。

此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。但是,不完美并不等于不伟大,伟大是一种本质,Java动态代理就是佐例。

JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了

CGLIB代理

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理,这就需要CGLIBCGLIB采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类的方法的调用,顺势植入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLIB动态代理均是实现Spring AOP的基础

1
2
3
4
5
6
7
8
9
10
11
12
public class RealImage{
private String fileName;
// 必须加 Superclass has no null constructors but no arguments were given
public RealImage() {
}
public RealImage(String fileName) {
this.fileName = fileName;
}
public void display() {
System.out.println("Displaying " + fileName);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CglibProxy implements MethodInterceptor {
private Object target;
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理前doSomething");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("代理后面doSomething");
return result;
}
}
1
2
3
4
5
6
7
8
public class CglibProxyTest {
public static void main(String[] args) {
RealImage realImage = new RealImage("http");
CglibProxy cglibProxy = new CglibProxy();
RealImage proxyInstance =(RealImage) cglibProxy.getInstance(realImage);
proxyInstance.display();
}
}

总结

CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理