设计模式-代理模式

模式定义

给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用。

主要作用

通过引入代理对象的方式来间接访问目标对象。

解决的问题

防止直接访问目标对象给系统带来的不必要复杂性。

模式原理

UML类图

代理模式

模式组成

  • 抽象主题角色(Subject):抽象主题类 的主要职责是声明 真实主题 与 代理 的共同接口方法,该类可以是接口也可以是抽象类;

  • 真实主题角色(RealSubject):该类也被称为 被委托类 或 被代理类,该类定义了代理所表示的真实对象,是负责执行系统真正的逻辑业务对象;

  • 代理主题角色(Proxy):也被称为 委托类 或 代理类,其内部持有 RealSubject 的引用,因此具备完全的对 RealSubject 的代理权;

模式优点

  1. 协调调用者和被调用者,降低了系统的耦合度。
  2. 代理对象作为客户端和目标对象之间的中介,起到了保护目标对象的作用。
  3. 职责清晰:真实主题角色 只负责实现实际的业务逻辑,无需关心其他非本职责的事务,通过后期代理的访问控制,可以扩展或缩减实际事务,符合设计模式 单一职责原则,代码简洁清晰;
  4. 高扩展性:真实主题角色 负责实际业务逻辑,可能经常性变动,但是由于其与 代理类 都实现了 抽象主题,因此,真实主题 的改变不会影响到代理类,符合设计模式 依赖倒置原则,里氏替换原则 和 开闭原则;
  5. 智能化:主要是基于动态代理的实现,可以在程序运行时生成一个合适的代理类;

模式缺点

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

应用场景

当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过也给代理对象来间接访问;

模式示例

示例背景

背景:假设希望买一台最新的顶配Mac电脑
冲突:国内还没上,只有美国才有
解决方案:寻找代购进行购买

示例步骤

静态代理

创建抽象对象接口(Subject)

声明你(真实对象)需要让代购(代理对象)帮忙做的事(买Mac)

1
2
3
4
5
6
7
8
9
10
11
12
13
package main.java.com.study.designPatterns.proxy.demoOne;

/**
* @author: whb
* @description: 抽象对象接口,声明你(真实对象)需要让代购(代理对象)帮忙做的事(买Mac)
*/
public interface Subject {

/**
* 买mac本
*/
public void buyMac();
}

创建真实对象类(RealSubject)

1
2
3
4
5
6
7
8
9
10
11
12
13
package main.java.com.study.designPatterns.proxy.demoOne;

/**
* @author: whb
* @description: 真实对象类
*/
public class RealSubject implements Subject {

@Override
public void buyMac() {
System.out.println("买一台mac本...");
}
}

创建代理对象类(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
package main.java.com.study.designPatterns.proxy.demoOne;

/**
* @author: whb
* @description: 创建代理对象
*/
public class ProxySubject implements Subject {

/**
* 引用真实对象示例
*/
private RealSubject realSubject;

public ProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}

@Override
public void buyMac() {
//调用真实对象的方法,进行代理购买
realSubject.buyMac();
//代理对象额外的操作
this.wrapMac();
}

public void wrapMac() {
System.out.println("用盒子包装好mac...");
}
}

客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
package main.java.com.study.designPatterns.proxy.demoOne;

/**
* @author: whb
* @description: 静态代理客户端调用
*/
public class StaticProxyClient {

public static void main(String[] args) {
Subject proxy = new StaticProxy();
proxy.buyMac();
}
}

上面代码描绘的代理模式,更加细分的话一般称为静态代理模式,为什么称为静态代理?因为代理对象需要与目标对象实现一样的接口(也就是RealSubject和ProxSubject实现buyMac接口)。
假设现在有这种情况,为了保守起见不仅找代购,还通过其他多种渠道购买,这种情况下就会有很多代理类,代理类如果太多会出现什么样的问题?
一旦接口增加方法,目标对象与代理对象都要维护,这样的话代码改起来就相当烦琐和冗余。为了解决这个问题,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
package main.java.com.study.designPatterns.proxy.demoOne;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
* @author: whb
* @description: 动态代理
*/
public class DynamicProxy {
/**
* 维护一个目标对象
*/
private Object object;

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

/**
* 给目标对象生成代理对象
*/
public Object getProxyInstance() {
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------------开始动态代理-----------" + "\n");
//执行目标方法
Object returnValue = method.invoke(object, args);
System.out.println("------------结束动态代理-----------");
return returnValue;
}
});
}
}

动态代理客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main.java.com.study.designPatterns.proxy.demoOne;

/**
* @author: whb
* @description: 动态代理客户端调用
*/
public class DynamicProxyClient {

public static void main(String[] args) {

//动态代理需要传入接口对象,也就是接口子类
Subject subject = new ProxySubject(new RealSubject());
//将接口库对象传进代理类,使用代理方法
Subject proxy = (Subject) new DynamicProxy(subject).getProxyInstance();
//执行方法【代理对象】
proxy.buyMac();
}
}

虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。


CGLIB动态代理

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

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
25
26
27
28
29
30
31
32
package main.java.com.study.designPatterns.proxy.cglibProxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
* @author: whb
* @description: Cglib代理类
*/
public class CglibProxy implements MethodInterceptor {

private Object target;

public Object getInstance(final 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 object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("----------开启事务---------");
Object result = methodProxy.invokeSuper(object, args);
System.out.println("-----------结束事务---------");
return result;
}
}

CGLIB代理测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main.java.com.study.designPatterns.proxy.cglibProxy;

/**
* @author: whb
* @description: Cglib动态代理测试
*/
public class TestCglibProxy {

public static void main(String[] args) {
UserDao userDao = new UserDaoImpl();
CglibProxy cglibProxy = new CglibProxy();
UserDaoImpl userDaoCglibProxy = (UserDaoImpl) cglibProxy.getInstance(userDao);
userDaoCglibProxy.save();
}
}

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

本文标题:设计模式-代理模式

文章作者:王洪博

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

最后更新:2019年09月12日 - 10:09

原始链接:http://whb1990.github.io/posts/e3d6aa41.html

▄︻┻═┳一如果你喜欢这篇文章,请点击下方"打赏"按钮请我喝杯 ☕
0%