设计模式之代理模式(文末赠书)

た 入场券 2022-12-14 06:22 240阅读 0赞

静态代理

在开始代理模式定义之前我们先看一段常见的业务逻辑,假设你有个接口ISubject,接口有个operator方法,然后有个具体的实现类来实现此方法:

  • 接口类

    public interface ISubject {

    1. void operator();

    }

  • 具体实现类

    public class RealSubject implements ISubject{

    1. @Override
    2. public void operator() {
    3. System.out.println("do something");
    4. }

    }

那现在你有个新的需求,就是在执行operator方法之前需要打印一段日志,但是不能修改原RealSubject的业务逻辑,那该怎么实现呢?

这时候大家伙肯定会想到建一个新的实现类来实现ISubject,并将RealSubject组合进来,真正的业务逻辑还是调用RealSubject的operator方法来实现,在执行operator之前实现我们需要的业务逻辑,比如日志打印。

  1. public class SubjectProxy implements ISubject{
  2. private RealSubject subject;
  3. public SubjectProxy(RealSubject subject) {
  4. this.subject = subject;
  5. }
  6. @Override
  7. public void operator() {
  8. System.out.println("this is log");
  9. subject.operator();
  10. }
  11. }

客户端调用的时候我们直接使用SubjectProxy来实现业务逻辑即可:

  1. public class Client {
  2. public static void main(String[] args) {
  3. RealSubject realSubject = new RealSubject();
  4. ISubject proxy = new SubjectProxy(realSubject);
  5. proxy.operator();
  6. }
  7. }

执行结果如下:

在这里插入图片描述

看到这里你可能会想,这就是代理模式?就这?

是的,这就是代理模式,理解了这个例子你就差不多掌握了代理模式,只不过目前还是静态代理,等会我们再讲讲如何实现动态代理。

让我们先来看看代理模式的定义。

代理模式定义

代理模式的类图结构如下:

在这里插入图片描述

图中的 Subject 是程序中的业务逻辑接口,RealSubject 是实现了 Subject 接口的真正业务类,Proxy 是实现了 Subject 接口的代理类,封装了一个 RealSubject 引用。在程序中不会直接调用 RealSubject 对象的方法,而是使用 Proxy 对象实现相关功能。

Proxy.operator() 方法的实现会调用其中封装的 RealSubject 对象的 operator() 方法,执行真正的业务逻辑。

这就是 “代理模式”。

通过上面的例子我们可以看出,使用代理模式可以在不修改被代理对象的基础上,通过扩展代理类来进行一些功能的附加与增强,值得注意的是,代理类和被代理类应该共同实现一个接口,或者共同继承某个类。

动态代理

上面例子中我们展示的是代理模式中的 “静态代理模式”,这是因为我们需要预先定义好代理类SubjectProxy,当需要代理的类很多时,就会出现很多的Proxy类。

在这种场景下我们就需要使用动态代理了,动态代理的实现方式又有很多种,本章我们来看看JDK原生的动态代理。

JDK动态代理的代码实现:

  • 动态代理类

    public class SubjectInvokerHandler implements InvocationHandler {

    1. //真正的业务对象
    2. private Object target;
    3. public SubjectInvokerHandler(Object target) {
    4. this.target = target;
    5. }
    6. @Override
    7. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    8. System.out.println("执行前置业务逻辑");
    9. //真正的业务逻辑
    10. method.invoke(target, args);
    11. System.out.println("执行后置业务逻辑");
    12. return null;
    13. }
    14. public Object getProxy() {
    15. //创建代理对象
    16. return Proxy.newProxyInstance(
    17. Thread.currentThread().getContextClassLoader(),
    18. target.getClass().getInterfaces(),
    19. this
    20. );
    21. }

    }

动态代理的核心就是InvocationHandler 接口,它只有一个invoke()方法,这个方法决定了如何处理传递过来的方法调用。

  • 客户端:

    public class Client {

    1. public static void main(String[] args) {
    2. ISubject realSubject = new RealSubject();
    3. SubjectInvokerHandler invokerHandler = new SubjectInvokerHandler(realSubject);
    4. //获取代理对象
    5. ISubject proxy = (ISubject) invokerHandler.getProxy();
    6. proxy.operator();
    7. }

    }

对于需要相同代理逻辑的业务类,只需要提供一个 InvocationHandler 接口实现类即可。在 Java 运行的过程中,JDK会为每个 RealSubject 类动态生成相应的代理类并加载到 JVM 中,然后创建对应的代理实例对象,返回给上层调用者。

  • 执行效果:

在这里插入图片描述

可以看到我们这里并没有像静态代理一样实现具体的代理类,但是最终却实现了同样的效果。

JDK动态代理实现原理

了解了动态代理的基本使用后我们来看看动态代理的实现原理。

创建动态代理的入口类是Proxy.newProxyInstance()这个静态方法,它的三个参数分别是加载动态生成的代理类的类加载器、业务类实现的接口和InvocationHandler对象。(代码有点长,我们截取一下):

  1. public static Object newProxyInstance(ClassLoader loader,
  2. Class<?>[] interfaces,
  3. InvocationHandler h)
  4. throws IllegalArgumentException
  5. {
  6. final Class<?>[] intfs = interfaces.clone();
  7. //获取代理类
  8. Class<?> cl = getProxyClass0(loader, intfs);
  9. try {
  10. ...
  11. //获取代理类的构造方法
  12. final Constructor<?> cons = cl.getConstructor(constructorParams);
  13. final InvocationHandler ih = h;
  14. return cons.newInstance(new Object[]{ h});
  15. ...
  16. }
  17. ...
  18. }

newProxyInstance()最终返回一个实例,它是通过 cl 这个 Class 文件的构造方法反射生成。cl 则由 getProxyClass0() 方法获取,代码如下:

  1. private static Class<?> getProxyClass0(ClassLoader loader,
  2. Class<?>... interfaces) {
  3. ...
  4. // 如果指定的类加载器中已经创建了实现指定接口的代理类,则查找缓存;
  5. // 否则通过ProxyClassFactory创建实现指定接口的代理类
  6. return proxyClassCache.get(loader, interfaces);
  7. }

proxyClassCache 是定义在 Proxy 类中的静态字段,主要用于缓存已经创建过的代理类,定义如下:

  1. private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
  2. proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

WeakCache.get() 方法会首先尝试从缓存中查找代理类,如果查找不到,则会创建 Factory 对象并调用其 get() 方法获取代理类。Factory 是 WeakCache 中的内部类,Factory.get() 方法会调用 ProxyClassFactory.apply() 方法创建并加载代理类。

ProxyClassFactory.apply() 方法首先会检测代理类需要实现的接口集合,然后确定代理类的名称,之后创建代理类并将其写入文件中,最后加载代理类,返回对应的 Class 对象用于后续的实例化代理类对象。该类的核心代码如下:

  1. private static final class ProxyClassFactory
  2. implements BiFunction<ClassLoader, Class<?>[], Class<?>>{
  3. // 代理类的前缀是 $Proxy
  4. private static final String proxyClassNamePrefix = "$Proxy";
  5. private static final AtomicLong nextUniqueNumber = new AtomicLong();
  6. @Override
  7. public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
  8. ...
  9. long num = nextUniqueNumber.getAndIncrement();
  10. //代理类的名称是通过包名、代理类名称前缀以及编号这三项组成的
  11. String proxyName = proxyPkg + proxyClassNamePrefix + num;
  12. /* * 生成代理类,并写入文件 */
  13. byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
  14. proxyName, interfaces, accessFlags);
  15. try {
  16. return defineClass0(loader, proxyName,
  17. proxyClassFile, 0, proxyClassFile.length);
  18. } catch (ClassFormatError e) {
  19. ...
  20. }
  21. }
  22. }

小结

JDK动态代理的实现原理是动态创建代理类并通过指定类加载器进行加载,在创建代理对象时将InvocationHandler对象作为构造参数传入。当调用代理对象时,会调用 InvocationHandler.invoke() 方法,从而执行代理逻辑,并最终调用真正业务对象的相应方法。

总结

本章内容主要是讲设计模式中的代理模式,代理模式的作用就是在不修改被代理对象的源码上,进行功能增强。这种开发模式在AOP面向切面编程领域很常见。

代理模式中静态代理需要自己编写代理类,动态代理中代理类通过Proxy.newInstance()方法生成,他们的实质都是面向接口编程。

那么如果不是接口又想实现代理怎么办呢?咱们下期见。不对,咱们还要先送书!


赠书

《JAVA 核心编程》 柳伟卫

在此感谢清华大学出版社的赞助。

赠书方式

简单粗暴,在公众号本文中留言,留言内容点赞数最高者获得一本。

时间截止10月15日的20点整。


这里为大家准备了一份小小的礼物,关注公众号,输入如下代码,即可获得百度网盘地址,无套路领取!

001:《程序员必读书籍》
002:《从无到有搭建中小型互联网公司后台服务架构与运维架构》
003:《互联网企业高并发解决方案》
004:《互联网架构教学视频》
006:《SpringBoot实现点餐系统》
007:《SpringSecurity实战视频》
008:《Hadoop实战教学视频》
009:《腾讯2019Techo开发者大会PPT》

010: 微信交流群

在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,240人围观)

还没有评论,来说两句吧...

相关阅读

    相关 设计模式代理模式

    代理模式是结构型模式中的一种相对简单的设计模式。代理模式是隔绝目标对象和访问对象的,使之没有直接访问关系。 使用代理模式的原因由两个:一个是保护目标对象,一个是增强目标对象。

    相关 设计模式代理模式

    设计模式 为其他对象提供一种代理以控制这个对象的访问。简单来说就是在一些情况下客户不想或者不能直接饮用一个对象,而代理对象可以在客户和目标对象之间起到中间作用,去掉客户不

    相关 设计模式代理模式

    前言 使用场景 1、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是

    相关 设计模式代理模式

    一:什么是代理? 代理是英文 Proxy 翻译过来的。我们在生活中见到过的代理,大概最常见的就是朋友圈中卖面膜的同学了。 她们从厂家拿货,然后在朋友圈中宣传,然后卖给熟人。

    相关 设计模式代理模式

    代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功