聊聊Java设计模式-代理模式

简介: 代理模式(Proxy Design Pattern)是为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象。被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象

代理模式(Proxy Design Pattern)是为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象。被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。

一、代理模式介绍

在结束创建型模式的讲解后,从这一篇开始就进入到了结构型模式,结构型模式主要是总结一些类和或对象组合在一起的结构。代理模式在不改变原始代理类的情况下,通过引入代理类来给原始类附加功能。

代理模式的主要结构如下:

  1. Subject:抽象主题类,通过接口或抽象类声明主题和代理对象实现的业务方法
  2. RealSubject:真实主题类,实现Subject中的具体业务,是代理对象所代表的真实对象
  3. Proxy:代理类,其内部含有对真实主题的引用,它可以访问、控制或扩展RealSubject的功能
  4. Client:客户端,通过使用代理类来访问真实的主题类

按照上面的类图,可以实现如下代码:

//主题类接口
public interface Subject {
   
   
    void Request();
}

//真实的主题类
public class RealSubject implements Subject{
   
   

    @Override
    public void Request() {
   
   
        System.out.println("我是真实的主题类");
    }
}

//代理类
public class Proxy implements Subject{
   
   

    private RealSubject realSubject;

    @Override
    public void Request() {
   
   
        if (realSubject == null) {
   
   
            realSubject = new RealSubject();
        }
        realSubject.Request();
    }
}

//客户端
public class Client {
   
   
    public static void main(String[] args) {
   
   
        Proxy proxy = new Proxy();
        proxy.Request();
    }
}

代理模式有比较广泛的使用,比如Spring AOPRPC、缓存等。在 Java 中,根据代理的创建时期,可以将代理模式分为静态代理和动态代理,下面就来分别阐述。

二、代理模式实现

动态代理和静态代理的区分就是语言类型是在运行时检查还是在编译期检查。

2.1 静态代理

静态代理是指在编译期,也就是在JVM运行之前就已经获取到了代理类的字节码信息。即Java源码生成.class文件时期:

由于在JVM运行前代理类和真实主题类已经是确定的,因此也被称为静态代理。

在实际使用中,通常需要定义一个公共接口及其方法,被代理对象(目标对象)与代理对象一起实现相同的接口或继承相同的父类。其实现代码就是第一节中的代码。

2.2 动态代理

动态代理,也就是在JVM运行时期动态构建对象和动态调用代理方法。

常用的实现方式是反射。反射机制是指程序在运行期间可以访问、检测和修改其本身状态或行为的一种能力,使用反射我们可以调用任意一个类对象,以及其中包含的属性及方法。比如JDK Proxy。

此外动态代理也可以通过ASM(Java 字节码操作框架)来实现。比如CGLib。

2.2.1 JDK Proxy

这种方式是JDK自身提供的一种方式,它的实现不需要引用第三方类,只需要实现InvocationHandler接口,重写invoke()方法即可。代码实现如下所示:

public class ProxyExample {
   
   

    static interface Car {
   
   
        void running();
    }
    static class Bus implements Car {
   
   
        @Override
        public void running() {
   
   
            System.out.println("bus is running");
        }
    }
    static class Taxi implements Car {
   
   
        @Override
        public void running() {
   
   
            System.out.println("taxi is runnig");
        }
    }
    //核心部分 JDK Proxy 代理类
    static class JDKProxy implements InvocationHandler {
   
   
        private Object target;

        public Object getInstance(Object target) {
   
   
            this.target = target;
            //获得代理对象
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
         }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   
   
            Object result = method.invoke(target, args);
            return result;
        }
    }

    public static void main(String[] args) {
   
   
        JDKProxy jdkProxy = new JDKProxy();
        Car instance = (Car) jdkProxy.getInstance(new Taxi());
        instance.running();
    }
}

动态代理的核心是实现Invocation接口,我们再看看Invocation接口的源码:

public interface InvocationHandler {
   
   

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

实际上是通过invoke()方法来触发代理的执行方法。最终使得实现Invocation接口的类具有动态代理的能力。

动态代理的好处在于不需要和静态代理一样提前写好公共的代理接口,只需要实现Invocation接口就可拥有动态代理能力。下面我们再来看看 CGLib 是如何实现的

2.2.2 CGLib

CGLib 动态代理采取的是创建目标类的子类的方式,通过子类化,我们可以达到近似使用被调用者本身的效果。其实现代码如下所示:

public class CGLibExample {
   
   

    static class car {
   
   
        public void running() {
   
   
            System.out.println("car is running");
        }
    }

    static 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 {
   
   
            Object result = methodProxy.invokeSuper(o, objects);
            return result;
        }
    }

    public static void main(String[] args) {
   
   
        CGLibProxy cgLibProxy = new CGLibProxy();
        car instance = (car) cgLibProxy.getInstance(new car());
        instance.running();
    }
}

从代码可以看出CGLib 也是通过实现代理器的接口,然后再调用某个方法完成动态代理,不同的是CGLib在初始化被代理类时,是通过Enhancer对象把代理对象设置为被代理类的子类来实现动态代理:

Enhancer enhancer = new Enhancer();
//设置父类为实例类
enhancer.setSuperclass(this.target.getClass());
//回调方法
enhancer.setCallback(this);
//创建代理对象
return enhancer.create();

2.2.3 JDK Proxy 和 CGLib 的区别

  1. 来源:JDK Proxy 是JDK 自带的功能,CGLib 是第三方提供的工具
  2. 实现:JDK Proxy 通过拦截器加反射的方式实现;CGLib 基于ASM实现,性能比较高
  3. 接口:JDK Proxy 只能代理继承接口的类,CGLib 不需要通过接口来实现,它是通过实现子类的方式来完成调用

三、代理模式的应用场景

3.1 MapperProxyFactory

在MyBatis 中,也存在着代理模式的使用,比如MapperProxyFactory。其中的newInstance()方法就是生成一个具体的代理来实现功能,代码如下:

public class MapperProxyFactory<T> {
   
   
  private final Class<T> mapperInterface;

  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
   
   
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
   
   
    return mapperInterface;
  }

  public Map<Method, MapperMethodInvoker> getMethodCache() {
   
   
    return methodCache;
  }

  // 创建代理类
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
   
   
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {
   
    mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
   
   
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
}

3.2 Spring AOP

代理模式最常使用的一个应用场景就是在业务系统中开发一些非功能性需求,比如监控、统计、鉴权、限流、事务、日志等。将这些附加功能与业务功能解耦,放在代理类中统一处理,让程序员只需要关注业务方面的开发。而Spring AOP 的切面实现原理就是基于动态代理

Spring AOP 的底层通过上面提到的 JDK Proxy 和 CGLib动态代理机制,为目标对象执行横向织入。当Bean实现了接口时, Spring就会使用JDK Proxy,在没有实现接口时就会使用 CGLib。也可以在配置中强制使用 CGLib:

<aop:aspectj-autoproxy proxy-target-class="true"/>

3.3 RPC 框架的封装

RPC 框架的实现可以看作成是一种代理模式,通过远程代理、将网络同步、数据编解码等细节隐藏起来,让客户端在使用 RPC 服务时,不必考虑这些细节。

参考资料

http://c.biancheng.net/view/1359.html

https://kaiwu.lagou.com/course/courseInfo.htm?courseId=59#/detail/pc?id=1768

https://time.geekbang.org/column/article/7489

https://time.geekbang.org/column/article/201823

《Java 重学设计模式》

《大话设计模式》

目录
相关文章
|
20天前
|
设计模式 Java 程序员
[Java]23种设计模式
本文介绍了设计模式的概念及其七大原则,强调了设计模式在提高代码重用性、可读性、可扩展性和可靠性方面的作用。文章还简要概述了23种设计模式,并提供了进一步学习的资源链接。
35 0
[Java]23种设计模式
|
4天前
|
设计模式 JavaScript Java
Java设计模式:建造者模式详解
建造者模式是一种创建型设计模式,通过将复杂对象的构建过程与表示分离,使得相同的构建过程可以创建不同的表示。本文详细介绍了建造者模式的原理、背景、应用场景及实际Demo,帮助读者更好地理解和应用这一模式。
|
1月前
|
设计模式 监控 算法
Java设计模式梳理:行为型模式(策略,观察者等)
本文详细介绍了Java设计模式中的行为型模式,包括策略模式、观察者模式、责任链模式、模板方法模式和状态模式。通过具体示例代码,深入浅出地讲解了每种模式的应用场景与实现方式。例如,策略模式通过定义一系列算法让客户端在运行时选择所需算法;观察者模式则让多个观察者对象同时监听某一个主题对象,实现松耦合的消息传递机制。此外,还探讨了这些模式与实际开发中的联系,帮助读者更好地理解和应用设计模式,提升代码质量。
Java设计模式梳理:行为型模式(策略,观察者等)
|
2月前
|
存储 设计模式 安全
Java设计模式-备忘录模式(23)
Java设计模式-备忘录模式(23)
|
2月前
|
设计模式 存储 算法
Java设计模式-命令模式(16)
Java设计模式-命令模式(16)
|
2月前
|
设计模式 存储 缓存
Java设计模式 - 解释器模式(24)
Java设计模式 - 解释器模式(24)
|
2月前
|
设计模式 安全 Java
Java设计模式-迭代器模式(21)
Java设计模式-迭代器模式(21)
|
2月前
|
设计模式 缓存 监控
Java设计模式-责任链模式(17)
Java设计模式-责任链模式(17)
|
2月前
|
设计模式 运维 算法
Java设计模式-策略模式(15)
Java设计模式-策略模式(15)
|
2月前
|
设计模式 算法 Java
Java设计模式-模板方法模式(14)
Java设计模式-模板方法模式(14)

热门文章

最新文章

  • 1
    C++一分钟之-设计模式:工厂模式与抽象工厂
    42
  • 2
    《手把手教你》系列基础篇(九十四)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-下篇(详解教程)
    46
  • 3
    C++一分钟之-C++中的设计模式:单例模式
    53
  • 4
    《手把手教你》系列基础篇(九十三)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-上篇(详解教程)
    37
  • 5
    《手把手教你》系列基础篇(九十二)-java+ selenium自动化测试-框架设计基础-POM设计模式简介(详解教程)
    61
  • 6
    Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
    56
  • 7
    Java面试题:设计模式在并发编程中的创新应用,Java内存管理与多线程工具类的综合应用,Java并发工具包与并发框架的创新应用
    40
  • 8
    Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
    49
  • 9
    Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
    105
  • 10
    Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
    75