Java特性之设计模式【代理模式】

一、代理模式

概述

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式

在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口

主要解决

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

何时使用

想在访问一个类时做一些控制

优缺点

优点:

  • 职责清晰
  • 高扩展性
  • 智能化

缺点:

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

注意事项

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

1. 各个角色介绍

1.1 抽象主题(Subject)

  • 定义了真实主题和代理主题的共同接口,这样在任何使用真实主题的地方都可以使用代理主题

1.2 真实主题(Real Subject)

  • 实现了抽象主题接口,是代理对象所代表的真实对象。客户端直接访问真实主题,但在某些情况下,可以通过代理主题来间接访问

1.3 代理(Proxy)

  • 实现了抽象主题接口,并持有对真实主题的引用。代理主题通常在真实主题的基础上提供一些额外的功能,例如延迟加载、权限控制、日志记录等

2. UML图

​ 我们将创建一个 ILandlordService 租房接口和实现了 ILandlordService 接口的实体类。AgentProxy 是一个代理类,减少房东 HostServiceImpl 对象加载的内存占用

3. 具体例子和代码

角色分配

  • ILandlordService :租房接口
    • HostServiceImpl:房东实现类
    • AgentProxy:中介代理

3.1 租房接口及其实现类

  • ILandlordService
java 复制代码
package com.vinjcent.prototype.proxy;

/**
 * @author vinjcent
 * @description 租房接口
 * @since 2024/3/27 17:52
 */
public interface ILandlordService {


    /**
     * 出租
     *
     * @param money 金额
     */
    void rent(Integer money);


}
  • HostServiceImpl
java 复制代码
package com.vinjcent.prototype.proxy;

/**
 * @author vinjcent
 * @description 房东实现类(实现)
 * @since 2024/3/27 17:55
 */
public class HostServiceImpl implements ILandlordService {

    @Override
    public void rent(Integer money) {
        System.out.println("房东处理...");
        System.out.println("出租" + money + "元一个月的房子");
    }

    @Override
    public String toString() {
        return "HostServiceImpl{}";
    }
}
  • AgentProxy
java 复制代码
package com.vinjcent.prototype.proxy.static_proxy;

import com.vinjcent.prototype.proxy.HostServiceImpl;
import com.vinjcent.prototype.proxy.ILandlordService;

/**
 * @author vinjcent
 * @description 中介代理
 * @since 2024/3/27 18:04
 */
public class AgentProxy implements ILandlordService {

    /**
     * 被代理的对象
     */
    private ILandlordService target;


    @Override
    public void rent(Integer money) {
        if (target == null) {
            target = new HostServiceImpl();
        }
        System.out.println("中介处理...");
        target.rent(money);
    }
}

3.2 额外拓展(动态代理:JDK动态代理、CGLib动态代理)

3.2.1 JDK动态代理

JDK动态代理的代理类根据目标实现的接口动态生成,不需要自己编写,生成的动态代理类和目标类都实现相同的接口,JDK动态代理的核心是InvocationHandler接口和Proxy类,缺点是目标类必须有实现接口,如果某个目标类没有实现接口,那么这个类就不能用JDK动态代理

  • JDKProxyFactory
java 复制代码
package com.vinjcent.prototype.proxy.dynamic_proxy;

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

/**
 * @author vinjcent
 * @description JDK动态代理
 * @since 2024/3/27 17:24
 */
public class JDKProxyFactory implements InvocationHandler {

    /**
     * 需要被代理的对象
     */
    private final Object object;

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

    @SuppressWarnings("unchecked")
    public <T> T getProxy() {
        Object o = Proxy.newProxyInstance(
                // 当前线程的上下文ClassLoader
                Thread.currentThread().getContextClassLoader(),
                // 代理对象实现的接口
                this.object.getClass().getInterfaces(),
                // 处理器自身
                this
        );

        return (T) o;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = new Object();
        // 进行方法匹配,调用对应方法名的方法
        if ("rent".equals(method.getName())) {
            System.out.println("JDK动态代理前置增强");
            result = method.invoke(object, args);
            System.out.println("JDK动态代理后置增强");
        }

        return result;

    }

    @Override
    public String toString() {
        return "JDKProxyFactory{" +
                "object=" + object +
                '}';
    }
}
3.2.2 CGLib动态代理

在程序运行时动态生成类的字节码,动态创建目标类的子类对象,在子类对象中增强目标类,CGLib是通过继承的方式实现的动态代理,因此如果某个类被标记为final,它是无法使用CGLib做动态代理的,优点在于不需要实现特定的接口,更加灵活

  • CglibProxyFactory
java 复制代码
package com.vinjcent.prototype.proxy.dynamic_proxy;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author vinjcent
 * @description CGLib动态代理
 * @since 2024/3/27 22:42:16
 */
public class CglibProxyFactory implements MethodInterceptor {

    @SuppressWarnings("unchecked")
    public <T> T getProxy(Class<T> clazz) {
        // Enhancer是CGLIB库中用于动态生成子类的主要类.通过创建Enhancer对象并设置需要代理的目标类、拦截器等参数,可以生成一个代理类
        Enhancer en = new Enhancer();
        // 设置代理的父类
        en.setSuperclass(clazz);
        // 设置方法回调
        en.setCallback(this);
        Object o = en.create();
        // 创建代理实例.通过调用Enhancer对象的create方法,可以生成一个代理对象.代理对象会继承目标类的方法,并且在调用代理对象的方法时会先调用拦截器的intercept方法,再执行目标方法
        return (T) en.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // 在生成代理类时,需要指定拦截器.拦截器是实现代理逻辑的关键,它会在代理类的方法被调用时拦截调用,并执行相应的逻辑

        Object result = null;
        System.out.println("CGLIB动态代理前置增强");
        // 通过调用代理对象的方法,会触发拦截器的intercept方法.在intercept方法中,可以根据需要执行各种逻辑,比如添加日志、性能统计、事务管理等
        if ("rent".equals(method.getName())) {
            // 通过继承的方法实现代理,因此这里调用invokeSuper
            result = methodProxy.invokeSuper(o, args);
        }
        System.out.println("CGLIB动态代理后置增强");

        return result;

    }
}

3.3 测试主函数

java 复制代码
package com.vinjcent.prototype.proxy;

import com.vinjcent.prototype.proxy.dynamic_proxy.CglibProxyFactory;
import com.vinjcent.prototype.proxy.dynamic_proxy.JDKProxyFactory;
import com.vinjcent.prototype.proxy.static_proxy.AgentProxy;

/**
 * @author vinjcent
 * @description 代理模式
 * @since 2024/3/27 18:01
 */
public class Main {


    public static void main(String[] args) {

        System.out.println("静态代理");
        // 静态代理
        ILandlordService landlordService = new AgentProxy();
        System.out.println("第一次代理调用目标类方法");
        landlordService.rent(20);
        System.out.println("\n第二次代理调用目标类的方法");
        landlordService.rent(25);
        System.out.println("\n");

        HostServiceImpl hostService = new HostServiceImpl();
        System.out.println("JDK动态代理");
        // JDK动态代理
        ILandlordService jdkProxy = new JDKProxyFactory(hostService).getProxy();
        jdkProxy.rent(750);

        System.out.println("\n");

        System.out.println("CGLib动态代理");
        // CGLIB动态代理
        HostServiceImpl cglibProxy = new CglibProxyFactory().getProxy(hostService.getClass());
        cglibProxy.rent(750);
    }

}
  • 测试结果

4. 使用场景

  • 远程代理
  • 虚拟代理
  • Copy-on-Write 代理
  • 保护(Protect or Access)代理
  • Cache代理
  • 防火墙(Firewall)代理
  • 同步化(Synchronization)代理
  • 智能引用(Smart Reference)代理
相关推荐
卡尔特斯1 小时前
Android Kotlin 项目代理配置【详细步骤(可选)】
android·java·kotlin
白鲸开源1 小时前
Ubuntu 22 下 DolphinScheduler 3.x 伪集群部署实录
java·ubuntu·开源
ytadpole1 小时前
Java 25 新特性 更简洁、更高效、更现代
java·后端
纪莫2 小时前
A公司一面:类加载的过程是怎么样的? 双亲委派的优点和缺点? 产生fullGC的情况有哪些? spring的动态代理有哪些?区别是什么? 如何排查CPU使用率过高?
java·java面试⑧股
JavaGuide2 小时前
JDK 25(长期支持版) 发布,新特性解读!
java·后端
用户3721574261352 小时前
Java 轻松批量替换 Word 文档文字内容
java
白鲸开源2 小时前
教你数分钟内创建并运行一个 DolphinScheduler Workflow!
java
晨米酱3 小时前
JavaScript 中"对象即函数"设计模式
前端·设计模式
Java中文社群3 小时前
有点意思!Java8后最有用新特性排行榜!
java·后端·面试
代码匠心3 小时前
从零开始学Flink:数据源
java·大数据·后端·flink