今天所要介绍的是设计模式中的代理模式,本文主要从以下几点出发:什么是代理模式、代理模式有哪些应用以及简易的实现代理模式。
一、什么是代理模式?
1.1 概述
在 GoF 中,是这样表述代理模式的:代理模式,为其他对象提供一种代理以控制对这个对象的访问。也就是说,代理模式它运行通过代理对象来控制对真实对象的访问。
在代理模式中,有三个关键角色,他们分别是:
- 抽象主题(Subject):定义了真实主题和代理主题的共同接口。抽象主题可以是接口或抽象类。
- 真实主题(Real Subject):定义了代理所代表的真实对象,是我们希望访问的目标对象,代理对象将会委托真实主题进行实际的操作。
- 代理主题(Proxy):持有对真实主题的引用,并实现了与抽象主题相同的接口。代理主题可以在调用真实主题之前或之后执行一些额外的操作,一实现对真实主题的控制。
1.2 核心思想
代理模式的核心思想是:通过引入代理来间接访问真实对象,以实现对真实对象的控制和增强。
1.3 代理模式的优点
- 可以实现对客户端和真实对象之间的解耦。客户端可以通过代理对象来访问真实对象,而无需直接与真实对象进行交互。这样可以有效降低系统的耦合度,是系统各部分可以独立进行修改和演化。
- 可以增加额外的功能。代理对象可以在调用真实对象的方法前后执行一些附加操作,例如权限验证、缓存、日志记录、性能监控等等。这样可以在不修改真实对象的情况下,通过代理对象对系统进行功能的增强。
1.4 应用场景
- 远程代理:当需要访问远程对象时,可以通过代理对象来进行网络通信和数据传输。
- 虚拟代理:当创建和初始化真实对象需要较大开销时,可以通过代理对象延迟加载真实对象,提高系统的性能。
- 保护代理:当需要控制对真实对象的访问权限时,可以通过代理对象来进行权限验证和控制
1.5 分类
代理模式可以分为,静态代理和动态代理。
二、静态代理
2.1 什么是静态代理?
静态代理是在编译时就已经确定代理类和真实类的关系,代理类是通过手动编写的方式创建的。
2.2 如何实现静态代理?
要实现静态代理,可以从以下步骤入手:
- 创建一个接口,定义代理类和被代理类共同实现的方法。
- 创建被代理类,实现这个接口,并且在其中定义实现方法。
- 创建代理类,也要实现这个接口,同时在其中定义一个被代理类的对象作为成员变量。
- 在代理类中实现接口中的方法,方法中调用被代理类中的对应方法。
- 通过创建代理对象,并调用其方法,方法增强。
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 实现步骤
- 定义一个接口,声明需要代理的方法。
- 创建一个被代理类,实现这个接口,并在其中定义实现方法。
- 创建一个代理类,实现
InvocationHandler
接口,并在其中定义一个被代理类的对象作为属性。 - 在使用代理类时,创建被代理类的对象和代理类的对象,并使用
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 实现步骤
- 导入CGLIB依赖。
- 创建一个被代理类,定义需要被代理的方法。
- 创建一个方法拦截器类,实现
MethodInterceptor
接口,并在其中定义一个被代理类的对象作为属性。 - 在使用代理类时,创建被代理类的对象和代理类的对象,并使用
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实现的话,则不需要实现接口,它是指定一个类生成其子类来实现动态代理的。