Java设计模式之代理模式

代理模式是一种结构型设计模式,其目的是在不改变原始类(被代理类)的前提下,通过引入代理类来间接访问原始类,并且可以在代理类中添加额外的逻辑或控制访问原始类的方式。

代理模式通常涉及三个角色:

1.抽象主题(Subject):定义了代理类和被代理类的共同接口,这样在任何使用被代理类的地方都可以使用代理类。

2.具体主题(Real Subject):即被代理类,实现了抽象主题接口,定义了代理类所代表的真实对象。

3.代理(Proxy):持有一个引用,可以访问被代理对象,并且提供与被代理对象相同的接口,从而可以在被代理对象的基础上增加额外的功能或控制访问。

代理模式的典型应用场景包括:

1.远程代理:通过代理类来访问远程服务器上的对象。

2.虚拟代理:延迟创建开销较大的对象,直到真正需要使用时才进行创建。

3.保护代理:控制对原始对象的访问权限,例如权限验证等。

4.缓存代理:为经常访问的数据提供一个临时存储,以提高访问速度。

5.AOP(面向切面编程):通过代理类在不修改原有代码的情况下,增加额外的功能,例如日志记录、性能监控等。

同样的代理模式也分很多种

以下是一些常见的代理模式的变体:

1.静态代理(Static Proxy):

静态代理是在编译时就已经确定好代理关系的代理模式。 代理类需要显式地声明代理对象,并在编译期间生成代理类的代码。

2.动态代理(Dynamic Proxy):

动态代理是在运行时生成代理对象的代理模式。 代理类不需要显式地声明代理对象,而是在运行时通过反射等机制动态地创建代理对象。

3.远程代理(Remote Proxy):

远程代理为位于不同地址空间的对象提供本地代表。 客户端通过代理类访问远程对象,代理类负责与远程对象通信并转发请求。

4.虚拟代理(Virtual Proxy):

虚拟代理延迟创建开销较大的对象,直到真正需要使用时才进行创建。 代理类在真正需要使用对象时才创建并持有对象的引用。

5.保护代理(Protection Proxy):

保护代理控制对原始对象的访问权限,例如权限验证等。 代理类在执行方法前进行权限验证或其他安全检查。

6.缓存代理(Cache Proxy):

缓存代理为经常访问的数据提供一个临时存储,以提高访问速度。代理类在执行方法前先检查缓存,如果缓存中存在数据,则直接返回缓存数据,否则调用原始对象的方法并将结果缓存起来。

7.智能引用代理(Smart Reference Proxy):

智能引用代理在对对象的方法进行调用时,执行额外的操作,例如记录对象的引用次数等。代理类可以跟踪对象的引用次数,并在引用次数为零时自动释放对象资源。

这些是代理模式的一些常见变体,根据具体的需求和场景,可以选择合适的代理模式来解决问题。

这里只抽几个常见的讲一下

静态代理

静态代理是代理模式的一种实现方式,它在编译时就已经确定好代理关系,代理类和被代理类的关系在程序运行前就已经确定。在静态代理中,代理类需要显式地声明代理对象,并在编译期间生成代理类的代码。

直接上代码

首先是主题

java 复制代码
// 抽象主题
interface Subject {
    void request();
}

然后是具体的实现类

java 复制代码
// 具体主题
class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject: Handling request.");
    }
}

接着就是我们的代理类

java 复制代码
// 代理
class Proxy implements Subject {
    private RealSubject realSubject;

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

    @Override
    public void request() {
        System.out.println("Proxy: Pre-processing request.");
        realSubject.request();
        System.out.println("Proxy: Post-processing request.");
    }
}

测试一下

java 复制代码
public class Test {

    public static void main(String[] args) {
        // 创建真实主题对象
        RealSubject realSubject = new RealSubject();
        // 创建代理对象,并将真实主题对象传递给代理对象
        Proxy proxy = new Proxy(realSubject);
        // 通过代理对象调用请求方法
        proxy.request();
    }
}

运行结果

从这里可以看出代理模式的主要作用其实就是在执行目标方法前后添加其它逻辑,然后不改变原有业务逻辑,这个就是代码的封装性和可复用性,其它模式大多也是为了实现这么个目的

动态代理

动态代理是在运行时生成代理对象的代理模式。与静态代理不同,动态代理不需要显式地声明代理对象,而是在运行时通过反射等机制动态地创建代理对象。Java 中的动态代理通常使用 java.lang.reflect.Proxy 类来实现。

直接上代码,一样的首先是抽象主题

java 复制代码
// 抽象主题
interface Subject {
    void request();
}

然后具体实现类

java 复制代码
// 具体主题
class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject: Handling request.");
    }
}

这里有些区别就是我们需要调用InvocationHandler处理器

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

// 调用处理器
class DynamicProxyHandler implements InvocationHandler {
    private Object realSubject;

    public DynamicProxyHandler(Object realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Dynamic Proxy: Pre-processing request.");
        Object result = method.invoke(realSubject, args);
        System.out.println("Dynamic Proxy: Post-processing request.");
        return result;
    }
}

测试一下

java 复制代码
import java.lang.reflect.Proxy;

public class Test {

    public static void main(String[] args) {
        // 创建真实主题对象
        RealSubject realSubject = new RealSubject();
        // 创建调用处理器
        DynamicProxyHandler handler = new DynamicProxyHandler(realSubject);
        // 创建代理对象
        Subject proxy = (Subject) Proxy.newProxyInstance(
                Subject.class.getClassLoader(),
                new Class<?>[] { Subject.class },
                handler
        );
        // 通过代理对象调用请求方法
        proxy.request();
    }
}

运行结果

这里有一点要注意,乍一看这玩意似乎和我们常见的拦截器HandlerInterceptor差不多,但是HandlerInterceptor是基于接口实现的,和这个不一样

不过MyBatis 在执行 SQL 映射接口时就使用了动态代理。MyBatis 中的 Mapper 接口并没有具体的实现类,而是由 MyBatis 在运行时动态生成的代理对象来实现的。当应用程序调用 Mapper 接口的方法时,实际上是调用了动态代理对象的对应方法,而动态代理对象会将方法调用转发给 SqlSession,最终执行相应的 SQL 语句。

动态代理在 Java 体系中有许多常见的应用场景,其中一些包括:

1.AOP(面向切面编程): AOP是一种编程范式,通过在程序中以切面的方式横向地抽取出逻辑功能,使得这些逻辑功能可以独立于业务逻辑而被重复使用。在 Java 中,AOP框架(如 Spring AOP)通常使用动态代理来实现横切逻辑,例如在方法执行前后添加日志记录、事务管理、性能监控等功能。

2.远程调用(RMI): 远程方法调用(RMI)是一种在分布式系统中进行远程通信的技术,它允许一个 Java 虚拟机中的对象调用另一个 Java虚拟机中的对象的方法。在 RMI中,可以使用动态代理来生成远程对象的代理对象,使得客户端可以通过代理对象来调用远程对象的方法,而不需要了解远程对象的具体实现细节。

3.Hibernate/Lazy Loading(延迟加载): 在 Hibernate 等ORM(对象关系映射)框架中,延迟加载是一种常见的优化技术,它可以延迟加载对象的关联属性,从而减少数据库查询次数和网络传输量。在Hibernate中,可以使用动态代理来生成延迟加载对象的代理对象,当客户端访问延迟加载对象的关联属性时,动态代理会触发数据库查询,并加载相关数据。

4.动态代理模式: 动态代理模式本身就是一种常见的应用场景,在一些情况下需要在运行时动态地生成代理对象来控制对其他对象的访问。例如,在 AOP中使用的动态代理就是一种常见的应用场景,还有一些其他的框架和库,如 Mockito、AspectJ 等,也都使用了动态代理来实现特定的功能。

总的来说,动态代理在 Java 体系中有着广泛的应用,它可以用于实现许多常见的功能和技术,如 AOP、远程调用、延迟加载等。

远程代理

远程代理是代理模式的一种变体,用于处理位于不同地址空间的对象,为客户端提供本地代表以访问远程对象。在远程代理中,代理对象充当客户端和远程对象之间的中介,隐藏了远程对象的实际位置和实现细节,使得客户端可以像访问本地对象一样访问远程对象。

还是先定义一个抽象主题

java 复制代码
// 抽象主题
interface Image {
    void display();
}

然后实现这个主题

java 复制代码
// 具体主题
class RealImage implements Image {
    private String filename;

    public RealImage(String filename) {
        this.filename = filename;
        loadFromDisk();
    }

    @Override
    public void display() {
        System.out.println("Displaying " + filename);
    }

    private void loadFromDisk() {
        System.out.println("Loading " + filename);
    }
}

然后创建一个远程代理

java 复制代码
// 远程代理
class RemoteImageProxy implements Image {
    private String filename;
    private RealImage realImage;

    public RemoteImageProxy(String filename) {
        this.filename = filename;
    }

    @Override
    public void display() {
        if (realImage == null) {
            // 在需要时创建并连接到远程对象
            realImage = new RealImage(filename);
        }
        realImage.display();
    }
}

测试一下

java 复制代码
public class Test {

    public static void main(String[] args) {
        // 创建远程代理对象
        Image image = new RemoteImageProxy("test.jpg");
        // 通过代理对象调用请求方法
        image.display();
    }
}

执行结果

这个远程代理目前还没有看出和静态代理有太大区别,但是据说有以下作用

远程代理在分布式系统中有许多应用场景,其中一些包括:

远程服务调用:远程代理可以用于在客户端与远程服务器之间进行远程方法调用(RPC),使得客户端可以像调用本地对象一样调用远程对象的方法。这在分布式系统中是非常常见的场景,例如微服务架构中的服务调用、分布式计算系统中的任务调度等。

远程对象访问:远程代理可以用于访问位于远程服务器上的对象,使得客户端可以通过代理对象来访问远程对象的方法和属性,而无需了解远程对象的实际位置和通信细节。这在分布式系统中的分布式对象访问、分布式缓存访问等场景中很有用。

远程服务代理:远程代理可以用于代理远程服务,使得客户端可以通过代理对象来访问远程服务,而代理对象可以在客户端和远程服务器之间进行中间处理,例如负载均衡、服务治理、监控等。

安全代理:远程代理可以用于实现安全代理,用于保护远程对象的安全性。例如,在远程服务调用中可以使用安全代理来进行身份验证、授权、数据加密等安全操作,以确保通信的安全性和可靠性。

性能优化:远程代理可以用于优化系统性能,例如在分布式系统中可以使用远程代理来实现缓存代理、负载均衡、请求重试等功能,以提高系统的性能、可扩展性和可靠性。

总的来说,远程代理在分布式系统中有着广泛的应用,它可以帮助解决分布式系统中的许多常见问题,提高系统的灵活性、可维护性和可扩展性。

缓存代理

缓存代理是代理模式的一种变体,用于为经常访问的数据提供一个临时存储,以提高访问速度。在缓存代理中,代理对象在执行方法前先检查缓存,如果缓存中存在数据,则直接返回缓存数据,否则调用原始对象的方法获取数据,并将结果缓存起来,以备下次访问使用。

我们定义一个数据服务

java 复制代码
// 抽象主题
interface DataService {
    String getData(String key);
}

然后实现它

java 复制代码
// 具体主题
class RealDataService implements DataService {
    @Override
    public String getData(String key) {
        // 模拟从数据库或其他数据源获取数据的操作
        System.out.println("Fetching data for key: " + key);
        return "Data for key: " + key;
    }
}

再添加一个缓存代理

java 复制代码
import java.util.HashMap;
import java.util.Map;

// 缓存代理
class CachedDataServiceProxy implements DataService {
    private RealDataService realDataService;
    private Map<String, String> cache;

    public CachedDataServiceProxy() {
        this.realDataService = new RealDataService();
        this.cache = new HashMap<>();
    }

    @Override
    public String getData(String key) {
        if (cache.containsKey(key)) {
            System.out.println("Retrieving cached data for key: " + key);
            return cache.get(key);
        } else {
            String data = realDataService.getData(key);
            cache.put(key, data);
            return data;
        }
    }
}

测试一下

java 复制代码
public class Test {

    public static void main(String[] args) {
        // 创建缓存代理对象
        DataService dataService = new CachedDataServiceProxy();
        // 第一次获取数据,会调用真实服务并缓存结果
        System.out.println(dataService.getData("key1"));
        // 第二次获取相同的数据,直接从缓存中获取,不会调用真实服务
        System.out.println(dataService.getData("key1"));
    }
}

运行结果

这个缓存代理目前看来实用性不大,因为现在都在用Redis做缓存服务,目前也有一些好用的缓存框架比如Spring Cache等

相关推荐
m0_571957582 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
魔道不误砍柴功4 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2344 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨4 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
测开小菜鸟5 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity6 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天6 小时前
java的threadlocal为何内存泄漏
java
caridle6 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^7 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋37 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx