Spring中的静态和动态代理
Spring动态代理详解
AOP编程实现原理
这里对动态代理实现进行补充。
静态代理模式和动态代理模式区别
静态代理模式和动态代理模式都属于设计模式中的代理模式,它们用于在不改变原始对象的情况下控制对对象的访问。然而,它们在实现方式和适用场景上有一些区别。
静态代理模式:
- 在静态代理模式中,代理类是在编译时期就已经存在的,代理类和原始类都是事先定义好的。代理类负责将对原始对象的方法调用委托给原始对象(代理类负责将客户端的调用转发给原始对象,但在转发之前和之后,它可以执行一些额外的逻辑,实现对原始对象行为的控制和增强。这就是代理模式的核心思想。),同时可以在调用前后添加一些额外的逻辑。
优点:
- 编译时生成,性能相对较高。
- 可以在代理类中加入额外的逻辑,如日志记录、权限控制等。
缺点:
- 静态代理需要为每个被代理的类创建一个代理类,导致类的数量增加。
动态代理模式:
- 动态代理模式是在运行时生成代理类的方式。Java中的动态代理是通过反射机制在运行时创建代理类,无需为每个被代理的类都创建一个代理类。
- 动态代理主要涉及两个重要接口:InvocationHandler 和 Proxy。
- InvocationHandler 是一个接口,代理类实现这个接口来实现自定义的代理逻辑。
- Proxy 是一个类,提供了创建动态代理对象的静态方法。动态代理适用于需要在运行时为多个类生成代理的情况,如 AOP(面向切面编程)等。
优点:
- 动态代理减少了代理类的数量,可以为多个类生成代理。更加灵活,可以在运行时动态添加和修改代理逻辑。
缺点:
- 由于是在运行时生成代理类,相对于静态代理,性能略有下降。
区别总结:
- 静态代理是在编译时期就已经存在的代理类,而动态代理是在运行时生成的代理类。
- 静态代理需要为每个被代理的类创建一个代理类,动态代理可以为多个类生成代理。
- 静态代理性能相对较高,动态代理更加灵活。
- 动态代理适用于需要动态地添加、修改代理逻辑的情况,例如 AOP。
1、基于 JDK 的动态代理实现步骤
基于 JDK 的动态代理需要使用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口。我们依旧使用缓存代理的案例来实现,具体步骤如下:
(1)定义一个接口,声明需要代理的方法:
public interface DataQuery {
String query(String queryKey);
String queryAll(String queryKey);
}
(2)创建一个被代理类,实现这个接口,并在其中定义实现方法:
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";
}
}
(3)创建一个代理类,实现 InvocationHandler 接口,并在其中定义一个被代理类的对象作为属性。
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);
}
}
在代理类中,我们实现了 InvocationHandler 接口,并在其中定义了一个被代理类的对象作为属性。在 invoke 方法中,我们可以对被代理对象的方法进行增强,并在方法调用前后输出日志。
(4)在使用代理类时,创建被代理类的对象和代理类的对象,并使用 Proxy.newProxyInstance 方法生成代理对象。
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("--------------------");
}
}
在这个示例中,我们使用 Proxy.newProxyInstance 方法生成代理对象,并将代理对象转换成 **DataQuery 接口类型,以便调用其代理的方法。**在代理对象调用方法时,会调用 CacheInvocationHandler 类中的 invoke 方法,实现对被代理对象的方法的增强。
2、基于 CGLIB 的动态代理实现步骤
基于 CGLIB 的动态代理需要使用 net.sf.cglib.proxy.Enhancer 类和 net.sf.cglib.proxy.MethodInterceptor 接口。具体步骤如下:
(1)创建一个被代理类,定义需要被代理的方法。
public class DatabaseDataQuery {
public String query(String queryKey) {
// 他会使用数据源从数据库查询数据很慢
System.out.println("正在从数据库查询数据");
return "result";
}
public String queryAll(String queryKey) {
// 他会使用数据源从数据库查询数据很慢
System.out.println("正在从数据库查询数据");
return "all result";
}
}
(2)创建一个方法拦截器类,实现 MethodInterceptor 接口,并在其中定义一个被代理类的对象作为属性。
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);
}
}
在这个代理类中,我们实现了 MethodInterceptor 接口,并在其中定义了一个被代理类的对象作为属性。在 intercept 方法中,我们可以对被代理对象的方法进行增强,并在方法调用前后输出日志。
(3)在使用代理类时,创建被代理类的对象和代理类的对象,并使用 Enhancer.create 方法生成代理对象。
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");
}
}
在这个示例中,我们使用 Enhancer.create 方法生成代理对象,并将代理对象转换成 RealSubject 类型,以便调用 request 方法。在代理对象调用 request 方法时,会调用 DynamicProxy 类中的 intercept 方法,实现对被代理对象的增强。
在实际应用中,基于 CGLIB 的动态代理可以代理任意类,但是生成的代理类比较重量级。如果被代理类是一个接口,建议使用基于 JDK 的动态代理来实现,这也是spring的做法;如果被代理类没有实现接口或者需要代理的方法是 final 方法,建议使用基于 CGLIB 的动态代理来实现。
动态代理的应用场景
动态代理是一种代理模式,它在运行时动态生成代理对象,而无需提前创建具体的代理类。动态代理在 Java 中通常使用java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来实现。以下是一些动态代理的
使用场景:
-
日志记录: 使用动态代理可以在方法调用前后自动添加日志记录,从而跟踪方法的执行过程。这样可以方便地监控系统运行情况,诊断问题,而无需修改实际类的代码。
-
性能监控: 动态代理可用于测量方法执行的时间,以评估性能。在方法调用前后记录时间戳,然后计算时间差,就可以得到方法执行所需的时间。
-
事务管理: 在数据库操作中,动态代理可用于自动管理事务。在方法调用前开始一个事务,在方法成功执行后提交事务,如果发生异常,则回滚事务。这样可以确保数据的一致性和完整性。
-
权限验证: 使用动态代理可以在方法调用前进行权限验证,确保只有具有适当权限的用户才能访问受保护的资源。这可以提高系统的安全性。
-
缓存: 动态代理可用于实现方法结果的缓存。在方法调用前检查缓存,如果缓存中有结果,则直接返回,否则执行方法并将结果存入缓存。这可以提高程序的执行效率。
-
负载均衡与故障转移: 在分布式系统中,动态代理可以用于实现负载均衡和故障转移。代理对象根据某种策略(如轮询、随机等)选择一个可用的服务实例,并将请求转发给它。如果服务实例发生故障,代理对象可以自动选择另一个可用的实例。
-
API 速率限制: 使用动态代理,可以在方法调用前检查 API 请求速率是否超过预设的限制。如果超过限制,可以拒绝请求或将请求延迟一段时间后再执行。
-
数据验证: 在方法调用前,动态代理可以用于验证传入的参数是否符合预期的规则和约束。这有助于确保数据的有效性和一致性。
-
重试机制: 当方法调用失败时(例如,因为网络问题、服务不可用等原因),动态代理可以实现自动重试的机制。代理对象可以在一定的时间间隔内尝试重新执行方法,直到成功或达到最大重试次数。
-
懒加载与资源管理: 动态代理可以用于实现资源的懒加载和管理。例如,代理对象可以在第一次访问资源时才创建和初始化它。此外,代理对象还可以在资源不再需要时自动释放它,以减少内存占用和提高性能。
-
跨语言和跨平台调用: 动态代理可以实现跨语言和跨平台的对象调用。例如,一个 Java 客户端可以使用动态代理调用一个基于 Python 的服务。在这种情况下,代理对象会负责处理跨语言通信的细节,如序列化、反序列化和网络传输。
-
AOP(面向切面编程): 动态代理是实现 AOP 的一种方式。AOP 允许在程序运行时动态地插入和修改横切关注点(如日志记录、性能监控等),而无需修改实际代码。动态代理可以轻松地实现 AOP,以提高代码的可维护性和可重用性。
这些仅仅是动态代理的一些应用场景。动态代理是一种非常灵活的技术,可以用于解决许多不同类型的问题。在实际项目中,根据具体需求,可以将动态代理与其他设计模式结合使用。事实上,我们如果不是编写中间件产品,绝大部分的场景都是在spring环境下实现的,我们理解了代理的本是就是增强,他是aop的一种实现方式,工作中绝大部分的场景都是使用aop来实现的。