代理模式

概念

代理模式为其他对象提供一个代理,然后可以通过这个代理对象控制原对象的访问,代理对象只是作为访问者和被代理对象之间的一个中介

代理分为静态代理和动态代理,静态代理是指代理类在编译时就已经确定,而动态代理是指代理类在运行时动态生成,下面通过一些场景说一下

静态代理应用场景

缓存代理

缓存代理可以为一些耗时的重复的操作提供缓存,从而提高程序的运行效率,首先我们定义一个数据查询接口

1
2
3
public interface DataQuery {
String query(String queryKey);
}

然后实现一个真实查询类

1
2
3
4
5
6
7
public class DatabaseDataQuery implements DataQuery {
@Override
public String query(String queryKey) {
// 查询数据库并返回结果
return "Result from database: " + queryKey;
}
}

然后我们定义一个缓存类,同样实现数据查询接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CachingDataQueryProxy implements DataQuery {
private final DataQuery realDataQuery;
private final Map cache;
public CachingDataQueryProxy(DataQuery realDataQuery) {
this.realDataQuery = new DatabaseDataQuery();
cache = new HashMap<>();
}
@Override
public String query(String queryKey) {
String result = cache.get(queryKey);
if (result null) {
result = realDataQuery.query(queryKey);
cache.put(queryKey, result);
System.out.println("Result retrieved from database and added to cache.");
} else {
System.out.println("Result retrieved from cache.");
}
return result;
}
}

然后我们就可以使用缓存

1
2
3
4
5
6
7
8
9
10
11
12
public class Client {
public static void main(String[] args) {
DataQuery realDataQuery = new DatabaseDataQuery();
DataQuery cachingDataQueryProxy = new
CachingDataQueryProxy(realDataQuery);
String queryKey = "example_key";
// 第一次查询,从数据库中获取数据并将其缓存
System.out.println(cachingDataQueryProxy.query(queryKey));
// 第二次查询相同的数据,从缓存中获取
System.out.println(cachingDataQueryProxy.query(queryKey));
}
}

安全代理

跟上面缓存代理模式差不多,但是作用不是缓存,而是鉴权,这里就不做代码展示了

虚拟代理

虚拟代理用于延迟创建 虚拟或资源密集型对象,在实际使用对象时才创建,避免在未使用得情况就创建实际对象,从而节省资源

假设有一个图片类,从网络中加载图像,但是图像类型占用内存很大,所以我们希望在展示的时候才加载他

首先我们定义一个图片类

1
2
3
public interface Image {
void display();
}

然后定义实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class LargeImage implements Image {
private final String imageUrl;
public LargeImage(String imageUrl) {
this.imageUrl = imageUrl;
loadImageFromNetwork();
}
private void loadImageFromNetwork() {
System.out.println("Loading image from network: " + imageUrl);
// 真实的图像加载逻辑...
}
@Override
public void display() {
System.out.println("Displaying image: " + imageUrl);
}
}

然后我们创建虚拟代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class VirtualImageProxy implements Image {
private final String imageUrl;
private LargeImage largeImage;
public VirtualImageProxy(String imageUrl) {
this.imageUrl = imageUrl;
}
@Override
public void display() {
if (largeImage null) {
largeImage = new LargeImage(imageUrl);
}
largeImage.display();
}
}

调用虚拟方法

1
2
3
4
5
6
7
8
9
public class Client {
public static void main(String[] args) {
Image virtualImageProxy = new
VirtualImageProxy("https://example.com/large-image.jpg");
System.out.println("Image will not be loaded until it is displayed.");
// 调用 display() 方法时,才会创建并加载大型图片
virtualImageProxy.display();
}
}

远程代理

远程服务器定义服务接口和实现类,再由本地服务器实现接口代理类,调用远程实现类

静态代理总结

静态代理的流程大致如下

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

动态代理

Java中动态代理有两种方式,一种是JDK动态代理,一种是CGLIB的动态代理

动态代理是在程序运行过程中动态生成代理类,一般通过反射机制实现,无需手动编写代理类,大大降低代码的复杂度

JDK动态代理

首先定义一个接口

1
2
3
4
public interface DataQuery {
String query(String queryKey);
String queryAll(String queryKey);
}

创建一个被代理类,实现这个接口和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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";
}
}

创建代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class CacheInvocationHandler implements InvocationHandler {
private HashMap 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);
}
}

使用代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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
);
// 事实上调用query方法的使用,他是调用了invoke
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("++++++++++++++++++++++++++++++++++++");
// 事实上调用queryAll方法的使用,他是调用了invoke
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("--------------------");
}
}

CGLIB动态代理

创建被代理类

1
2
3
4
5
6
7
8
9
10
11
12
public class DatabaseDataQuery {
public String query(String queryKey) {
// 他会使用数据源从数据库查询数据很慢
System.out.println("正在从数据库查询数据");
return "result";
}
public String queryAll(String queryKey) {
// 他会使用数据源从数据库查询数据很慢
System.out.println("正在从数据库查询数据");
return "all result";
}
}

创建方法拦截器类,实现MethodInterceptor接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class CacheMethodInterceptor implements MethodInterceptor {
private HashMap 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);
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {
public static void main(String[] args) {
// cglib通过Enhancer
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");
}
}

基于 CGLIB 的动态代理可以代理任意类,但是生成的代理类比较重量级如果被代理类是一个接口,建议使用基于 JDK 的动态代理来实现

代理模式的应用

SpirngAop就是使用动态代理实现的,如果切面类实现了一个接口,那么spring就会基于JDK动态代理进行切面,如果切面类没有实现接口,那么就会基于CGLIB切面

动态道理应用场景

  1. 日志记录: 使用动态代理可以在方法调用前后自动添加日志记录,从而跟踪方法的执行过程。这样可以方便地监控系统运行情况,诊断问题,而无需修改实际类的代码。
  2. 性能监控: 动态代理可用于测量方法执行的时间,以评估性能。在方法调用前后记录时间戳,然后计算时间差,就可以得到方法执行所需的时间。
  3. 事务管理: 在数据库操作中,动态代理可用于自动管理事务。在方法调用前开始一个事务,在方法成功执行后提交事务,如果发生异常,则回滚事务。这样可以确保数据的一致性和完整性。
  4. 权限验证: 使用动态代理可以在方法调用前进行权限验证,确保只有具有适当权限的用户才能访问受保护的资源。这可以提高系统的安全性。
  5. 缓存: 动态代理可用于实现方法结果的缓存。在方法调用前检查缓存,如果缓存中有结果,则直接返回,否则执行方法并将结果存入缓存。这可以提高程序的执行效率。
  6. 负载均衡与故障转移: 在分布式系统中,动态代理可以用于实现负载均衡和故障转移。代理对象根据某种策略(如轮询、随机等)选择一个可用的服务实例,并将请求转发给它。如果服务实例发生故障,代理对象可以自动选择另一个可用的实例。
  7. API 速率限制: 使用动态代理,可以在方法调用前检查 API 请求速率是否超过预设的限制。如果超过限制,可以拒绝请求或将请求延迟一段时间后再执行。
  8. 数据验证: 在方法调用前,动态代理可以用于验证传入的参数是否符合预期的规则和约束。这有助于确保数据的有效性和一致性。
  9. 重试机制: 当方法调用失败时(例如,因为网络问题、服务不可用等原因),动态代理可以实现自动重试的机制。代理对象可以在一定的时间间隔内尝试重新执行方法,直到成功或达到最大重试次数。
  10. 懒加载与资源管理: 动态代理可以用于实现资源的懒加载和管理。例如,代理对象可以在第一次访问资源时才创建和初始化它。此外,代理对象还可以在资源不再需要时自动释放它,以减少内存占用和提高性能。
  11. 跨语言和跨平台调用: 动态代理可以实现跨语言和跨平台的对象调用。例如,个 Java 客户端可以使用动态代理调用一个基于 Python 的服务。在这种情况下,代理对象会负责处理跨语言通信的细节,如序列化、反序列化和网络传输。
  12. AOP(面向切面编程): 动态代理是实现 AOP 的一种方式。AOP 允许在程序运行时动态地插入和修改横切关注点(如日志记录、性能监控等),而无需修改实际代码。动态代理可以轻松地实现 AOP,以提高代码的可维护性和可重用性。