今天所要介绍的是设计模式中的代理模式,本文主要从以下几点出发:什么是代理模式、代理模式有哪些应用以及简易的实现代理模式。

一、什么是代理模式?

1.1 概述

在 GoF 中,是这样表述代理模式的:代理模式,为其他对象提供一种代理以控制对这个对象的访问。也就是说,代理模式它运行通过代理对象来控制对真实对象的访问。

在代理模式中,有三个关键角色,他们分别是:

  • 抽象主题(Subject):定义了真实主题和代理主题的共同接口。抽象主题可以是接口或抽象类。
  • 真实主题(Real Subject):定义了代理所代表的真实对象,是我们希望访问的目标对象,代理对象将会委托真实主题进行实际的操作。
  • 代理主题(Proxy):持有对真实主题的引用,并实现了与抽象主题相同的接口。代理主题可以在调用真实主题之前或之后执行一些额外的操作,一实现对真实主题的控制。

1.2 核心思想

代理模式的核心思想是:通过引入代理来间接访问真实对象,以实现对真实对象的控制和增强。

1.3 代理模式的优点

  1. 可以实现对客户端和真实对象之间的解耦。客户端可以通过代理对象来访问真实对象,而无需直接与真实对象进行交互。这样可以有效降低系统的耦合度,是系统各部分可以独立进行修改和演化。
  2. 可以增加额外的功能。代理对象可以在调用真实对象的方法前后执行一些附加操作,例如权限验证、缓存、日志记录、性能监控等等。这样可以在不修改真实对象的情况下,通过代理对象对系统进行功能的增强。

1.4 应用场景

  1. 远程代理:当需要访问远程对象时,可以通过代理对象来进行网络通信和数据传输。
  2. 虚拟代理:当创建和初始化真实对象需要较大开销时,可以通过代理对象延迟加载真实对象,提高系统的性能。
  3. 保护代理:当需要控制对真实对象的访问权限时,可以通过代理对象来进行权限验证和控制

1.5 分类

代理模式可以分为,静态代理和动态代理

二、静态代理

2.1 什么是静态代理?

静态代理是在编译时就已经确定代理类和真实类的关系,代理类是通过手动编写的方式创建的

2.2 如何实现静态代理?

要实现静态代理,可以从以下步骤入手:

  1. 创建一个接口,定义代理类和被代理类共同实现的方法。
  2. 创建被代理类,实现这个接口,并且在其中定义实现方法。
  3. 创建代理类,也要实现这个接口,同时在其中定义一个被代理类的对象作为成员变量。
  4. 在代理类中实现接口中的方法,方法中调用被代理类中的对应方法。
  5. 通过创建代理对象,并调用其方法,方法增强。

2.3 静态代理的实现

2.3.1 缓存代理

类似于我们将数据存入Redis来提高访问的速度。即,我们如果每次查询的时候都是从数据库中获取数据,这样会使得造成较高的资源浪费,为了解决这种问题,我们可以在一个类中创建一个缓存,每次要查询的时候,先去这个缓存查找,如果缓存没有,再从数据库中查询。在这里,创建缓存的类就是代理类,查询语句或者说查询的过程是被代理的对象。

下面来看具体实现:

创建数据访问接口:

public interface DataQuery {
    String query(String queryKey);
}

创建真实的数据查询类,也就是被代理类:

public class DatabaseDataQuery implements DataQuery {
    @Override
    public String query(String queryKey) {
        // 进行数据库查询
        return "我是从数据库中查询到的数据: " + queryKey;
    }
}

创建代理对象,也实现数据访问接口,同时在内部维护一个被代理类对象和缓存:

public class CacheDataQueryProxy implements DataQuery{
    private final DataQuery dataQuery;
    private final Map<String, String> cache;

    public CacheDataQueryProxy(DataQuery dataQuery) {
        this.dataQuery = dataQuery;
        cache = new HashMap<>(8);
    }


    @Override
    public String query(String queryKey) {

        String result = cache.get(queryKey);
        if (result == null) {
            result = dataQuery.query(queryKey);
            cache.put(queryKey, result);
            System.out.println("在缓存中未找到数据,已添加: " + queryKey + "-->" + result + " 到缓存中");
        } else {
            System.out.println("从代理缓存中获取数据");
        }

        return result;
    }
}

客户端:

public class Main {
    public static void main(String[] args) {
        DataQuery databaseDataQuery = new DatabaseDataQuery();
        DataQuery cacheDataQueryProxy = new CacheDataQueryProxy(databaseDataQuery);

        String queryKey = "TestKey";
        // 第一次查询
        System.out.println(cacheDataQueryProxy.query(queryKey));
        // 第二次查询
        System.out.println(cacheDataQueryProxy.query(queryKey));
    }
}
2.3.2 安全代理

其主要的目的是限制用户的访问,只有特定的用户才能访问到相应的页面(或查询到结果)。譬如,我们假设要进行敏感数据查询,如果某用户有权限,那么他可以查询到,否则,无法查询到。

数据查询接口

public interface DataQuery {
    String query(String userId);
}

敏感数据查询类,其实现了 DataQuery 接口

public class SensitiveDataQuery implements DataQuery{
    @Override
    public String query(String userId) {
        // 进行敏感数据查询
        return "查询到敏感数据了..." + userId;
    }
}

敏感数据查询代理类,也实现了 DataQuery 接口并维护了一个敏感数据查询类

public class SecurityProxy implements DataQuery {

    private final SensitiveDataQuery sensitiveDataQuery;
    private final UserAuthenticator userAuthenticator;

    public SecurityProxy(SensitiveDataQuery sensitiveDataQuery, UserAuthenticator userAuthenticator) {
        this.sensitiveDataQuery = sensitiveDataQuery;
        this.userAuthenticator = userAuthenticator;
    }

    @Override
    public String query(String userId) {

        if (userAuthenticator.hasPermission(userId)) {
            return sensitiveDataQuery.query(userId);
        } else {
            return "没有查询的权限!!" + userId;
        }
    }
}

用户权限类,用于模拟权限认证:

public class UserAuthenticator {
    private final List<String> authorizedUserIds;

    public UserAuthenticator() {
        // 模拟从数据库或配置文件中获取已授权的用户列表
        authorizedUserIds = Arrays.asList("user1", "user2", "user3");
    }

    public boolean hasPermission(String userId) {
        return authorizedUserIds.contains(userId);
    }
}

客户端:

public class Main {
    public static void main(String[] args) {
        SensitiveDataQuery sensitiveDataQuery = new SensitiveDataQuery();
        UserAuthenticator userAuthenticator = new UserAuthenticator();
        DataQuery securityProxy = new SecurityProxy(sensitiveDataQuery, userAuthenticator);

        String userId1 = "user1";
        String userId2 = "user4";

        // 有查询权限
        System.out.println(securityProxy.query(userId1));
        // 无查询权限
        System.out.println(securityProxy.query(userId2));
    }
}
2.3.3 其他静态代理实例

除了缓存代理和安全代理之外,静态代理的实现还有,虚拟代理和远程代理。什么是虚拟代理呢?就是在你访问的时候才创建它,比如说,你在浏览器上进行浏览网站的时候,那些图片是很耗费资源的,如果一开始就把一个网站的图片全都加载出来,那么你在访问的时候会显得很卡顿,所以需要在真正去访问的时候才去创建(加载)那些图片。

而远程代用于访问位于不同地址空间的对象。可以为本地对象提供与远程对象相同的接口,使得客户端可以透明地访问远程对象。

实现方式与上述的类似,这里不再赘述,有兴趣的小伙伴,可以去网上查找相关资料,然后进行编码,调试。

三、动态代理

3.1 什么是动态代理?

从静态代理中,我们可以看出,代理类与被代理类的耦合度其实是比较高的,因为我们需要手动编写代理类的代码。如果我们向要解除这个耦合,就需要使用到动态代理。

对于静态代理来说,我们手动编写代理类,代理类的代码在程序运行前就已经生成了。而对于动态代理来说,不需要手动编写代理类,它是在程序运行的过程中动态生成的,一般使用反射机制来实现。

一般来说,我们有两种方式来实现动态代理。一种是基于 JDK 的动态代理,一种是基于 CGLIB 的动态代理。

3.2 基于 JDK 的动态代理的实现

3.2.1 实现步骤
  1. 定义一个接口,声明需要代理的方法。
  2. 创建一个被代理类,实现这个接口,并在其中定义实现方法。
  3. 创建一个代理类,实现 InvocationHandler 接口,并在其中定义一个被代理类的对象作为属性。
  4. 在使用代理类时,创建被代理类的对象和代理类的对象,并使用 Proxy.newProxyInstance 方法生成代理对象。
3.2.2 代码实现

查询接口:

public interface DataQuery {
    String query(String queryKey);
    String queryAll(String queryKey);
}

被代理类:

public class DatabaseDataQuery implements DataQuery {
    @Override
    public String query(String queryKey) {
        // 他会使用数据源从数据库查询数据很慢
        System.out.println("正在从数据库查询数据");
        return "result";
    }

    @Override
    public String queryAll(String queryKey) {
        // 他会使用数据源从数据库查询数据很慢
        System.out.println("正在从数据库查询数据");
        return "all result";
    }
}

代理类:

public class CacheInvocationHandler implements InvocationHandler {

    private HashMap<String,String> cache = new LinkedHashMap<>(256);

    private DataQuery databaseDataQuery;

    public CacheInvocationHandler(DatabaseDataQuery databaseDataQuery) {
        this.databaseDataQuery = databaseDataQuery;
    }

    public CacheInvocationHandler() {
        this.databaseDataQuery = new DatabaseDataQuery();
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1、判断是哪一个方法
        String result = null;
        if("query".equals(method.getName())){
            // 2、查询缓存,命中直接返回
            result = cache.get(args[0].toString());
            if(result != null){
                System.out.println("数据从缓存重获取。");
                return result;
            }

            // 3、未命中,查数据库(需要代理实例)
            result = (String) method.invoke(databaseDataQuery, args);

            // 4、如果查询到了,进行缓存
            cache.put(args[0].toString(),result);
            return result;
        }

        // 当其他的方法被调用,不希望被干预,直接调用原生的方法
        return method.invoke(databaseDataQuery,args);
    }
}

客户端:

public class Main {

    public static void main(String[] args) {
        // jdk提供的代理实现,主要是使用Proxy类来完成
        // 1、classLoader:被代理类的类加载器
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // 2、代理类需要实现的接口数组
        Class[] interfaces = new Class[]{DataQuery.class};
        // 3、InvocationHandler
        InvocationHandler invocationHandler = new CacheInvocationHandler();

        DataQuery dataQuery = (DataQuery) Proxy.newProxyInstance(
                classLoader, interfaces, invocationHandler
        );

        String result = dataQuery.query("key1");
        System.out.println(result);
        System.out.println("--------------------");
        result = dataQuery.query("key1");
        System.out.println(result);
        System.out.println("--------------------");
        result = dataQuery.query("key2");
        System.out.println(result);
        System.out.println("++++++++++++++++++++++++++++++++++++");

        result = dataQuery.queryAll("key1");
        System.out.println(result);
        System.out.println("--------------------");
        result = dataQuery.queryAll("key1");
        System.out.println(result);
        System.out.println("--------------------");
        result = dataQuery.queryAll("key2");
        System.out.println(result);
        System.out.println("--------------------");
    }
}

3.3 基于 CGLIB 的动态代理的实现

3.3.1 实现步骤
  1. 导入CGLIB依赖。
  2. 创建一个被代理类,定义需要被代理的方法。
  3. 创建一个方法拦截器类,实现 MethodInterceptor 接口,并在其中定义一个被代理类的对象作为属性。
  4. 在使用代理类时,创建被代理类的对象和代理类的对象,并使用 Enhancer.create 方法生成代理对象。

注意,JDK17需要在添加以下参数 --add-opens java.base/java.lang=ALL-UNNAMED

3.3.2 代码实现

被代理类:

public class DatabaseDataQuery {

    public String query(String queryKey) {
        // 他会使用数据源从数据库查询数据很慢
        System.out.println("正在从数据库查询数据");
        return "result";
    }

    public String queryAll(String queryKey) {
        // 他会使用数据源从数据库查询数据很慢
        System.out.println("正在从数据库查询数据");
        return "all result";
    }
}

方法拦截器:

public class CacheMethodInterceptor implements MethodInterceptor {

    private HashMap<String,String> cache = new LinkedHashMap<>(256);

    private DatabaseDataQuery databaseDataQuery;

    public CacheMethodInterceptor() {
        this.databaseDataQuery = new DatabaseDataQuery();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 1、判断是哪一个方法
        String result = null;
        if("query".equals(method.getName())){
            // 2、查询缓存,命中直接返回
            result = cache.get(args[0].toString());
            if(result != null){
                System.out.println("数据从缓存重获取。");
                return result;
            }

            // 3、未命中,查数据库(需要代理实例)
            result = (String) method.invoke(databaseDataQuery, args);

            // 4、如果查询到了,进行呢缓存
            cache.put(args[0].toString(),result);
            return result;
        }

        return method.invoke(databaseDataQuery,args);
    }
}

客户端:

public class Main {
    public static void main(String[] args) {

        Enhancer enhancer = new Enhancer();
        // 设置他的父类
        enhancer.setSuperclass(DatabaseDataQuery.class);
        // 设置一个方法拦截器,用来拦截方法
        enhancer.setCallback(new CacheMethodInterceptor());
        // 创建代理类
        DatabaseDataQuery databaseDataQuery = (DatabaseDataQuery)enhancer.create();

        databaseDataQuery.query("key1");
        databaseDataQuery.query("key1");
        databaseDataQuery.query("key2");

    }
}

pom.xml

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

3.4 两种实现方式的区别

使用 JDK 自带的动态代理方式实现的话,需要被代理类实现一个接口。

如果使用CGLIB实现的话,则不需要实现接口,它是指定一个类生成其子类来实现动态代理的。

天行健,君子以自强不息