一、前言
今天学习Java的动态代理,我们来探讨动态代理的概念和应用。
二、内容
2.1 简介
(1)什么是动态代理
动态代理是Java中一种强大的编程机制,它允许我们在运行时创建代理对象,以代替直接访问对象。通常,动态代理用于实现横切关注点(cross-cutting concerns),如日志记录、事务管理和权限控制等,而无需修改原始类的代码。换句话说,动态代理能够以无侵入的方式为类的方法添加其他功能。代理类在其方法中调用目标类的方法,并可以在调用前后添加额外的逻辑。
Java中的动态代理主要依赖于Java的反射机制和代理对象的
InvocationHandler
接口。
(2)为什么需要动态代理
- 分离关注点:动态代理允许将关注点(如日志、事务、权限控制等)与核心业务逻辑分离,提高了代码的可维护性和可重用性。
- 减少重复代码:动态代理可以减少在不同类中相似的代理代码的重复编写,从而降低了代码冗余。
- 避免对原始类的修改:静态代理需要为每个需要代理的类编写一个代理类,而动态代理可以动态地创建代理,无需修改原始类的代码。
Java中的动态代理主要使用两个核心类来实现:
java.lang.reflect.Proxy
和java.lang.reflect.InvocationHandler
。动态代理不需要为每个目标类编写一个代理类,而是在运行时生成代理对象。
Proxy
类 :通过Proxy.getProxyClass
方法生成代理类的Class对象,然后通过反射机制创建代理对象。InvocationHandler
:代理对象的方法调用会委托给InvocationHandler
对象来处理。newProxyInstance
方法 :Proxy
类提供了newProxyInstance
方法,可以直接生成代理对象,隐藏了代理类的生成细节。
(3)动态代理 vs 静态代理
静态代理通常需要为每个目标类编写一个代理类,将前后逻辑添加到代理类中,然后在客户端使用代理类来间接调用目标类的方法以实现功能增强。然而,这种方法存在一些缺点,如高维护成本、对接口的依赖和对大规模项目的不适用。
与之相比,动态代理的优势包括:
- 代码灵活性:动态代理允许在运行时生成代理类,更加灵活,适应不断增长的业务需求。
- AOP编程:动态代理可以实现面向切面编程(AOP),将横切关注点从业务逻辑中分离出来,提高代码的可维护性。
- 解耦:在Web开发中,动态代理有助于实现数据层和业务层的分离,提高代码的可维护性。
- 无侵入式扩展:动态代理实现无侵入式的代码扩展,而静态代理可能导致代理类的代码量庞大,难以维护。
(4)应用场景
动态代理在多个领域中广泛应用,提供了一种灵活且非侵入性的方式来实现功能增强和控制。一些应用场景包括:
- Spring AOP :
Spring
框架利用动态代理实现面向切面编程(AOP),在方法执行前、后或异常时执行横切关注点的逻辑,如事务管理、日志记录等。 - RPC框架 :常见的RPC框架如
Apache Thrift
和gRPC
使用动态代理来创建远程服务的代理对象,使本地调用远程方法变得透明。 - 用户鉴权:动态代理可用于实施用户鉴权和权限控制,允许在方法执行前或后进行身份验证和授权检查。
- 日志记录:通过动态代理,可以在方法执行前后记录日志信息,以便监控和故障排除。
- 更多其他领域。
2.2 如何使用
(1)创建代理对象
在Java中,动态代理对象通常使用Proxy.newProxyInstance
方法创建。
java
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)
可以看到,这个方法接受三个参数:
- 类加载器 (
ClassLoader
):用于加载生成的代理类,通常是目标接口的类加载器。 - 要代理的接口 (
Class[]
):指定代理对象要实现的接口。 InvocationHandler
对象 (InvocationHandler
):用于处理方法调用的逻辑。
(2) InvocationHandler接口
InvocationHandler
是一个函数式接口,它包含一个invoke
方法,用于处理代理对象方法的调用。在invoke
方法中,你可以添加自定义的逻辑,如在方法执行前后执行额外的操作。
java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
在invoke
方法中,可以根据method
参数中的方法信息和args
参数中的参数来自定义方法的执行逻辑,例如在方法执行前后添加额外的操作。invoke
方法的返回值应该与被代理方法的返回值类型相匹配,并需要处理可能抛出的Throwable
异常。
2.3 示例
(1)需求
假设我们有一个明星接口 Star
,其中定义了明星可以唱歌和跳舞的方法。我们还有一个具体的明星类 BigStar
,它实现了 Star
接口。现在,我们希望使用动态代理来创建一个代理对象,控制对 BigStar
对象方法的访问,同时在方法执行前后添加额外的操作。
我们将创建一个经纪人来代理明星,以便在明星的行为前后添加一些额外的功能。
(2)明星接口和大明星类
首先,我们有一个明星接口 Star
,它定义了明星的行为,包括唱歌、跳舞和说唱。接着,我们有一个实现了 Star
接口的大明星类 BigStar
,它可以执行这些行为。
java
/**
* 明星接口: 唱跳rap
*/
interface Star {
// 唱歌
void sing(String name);
// 跳舞
void dance();
// rap
void rap();
}
/**
* 大明星:能唱能跳能rap
*/
class BigStar implements Star {
private String name;
public BigStar(String name) {
this.name = name;
}
// 唱歌
@Override
public void sing(String name){
System.out.println(this.name + "正在唱" + name);
}
// 跳舞
@Override
public void dance(){
System.out.println(this.name + "正在跳舞");
}
@Override
public void rap() {
System.out.println(this.name + "正在说唱");
}
// ... 其他方法和属性
}
(3)动态代理增强功能
现在,我们将创建一个经纪人类 DynamicProxy
,并使用动态代理来为大明星增强功能。经纪人将在大明星的行为前后添加额外的功能。
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy {
public static void main(String[] args) {
// 1. 创建大明星对象(也就是代理的对象)
BigStar bigStar = new BigStar("吉哥");
// 2. 创建一个 InvocationHandler 对象,用于处理方法调用(代理对象的处理程序)
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在方法执行前添加功能
System.out.println("经纪人准备安排行程...");
// 执行方法
Object result = method.invoke(bigStar, args);
// 在方法执行后添加功能
System.out.println("经纪人处理后事务...");
return result;
}
};
// 3. 创建代理对象
Star proxy = (Star) Proxy.newProxyInstance(
Star.class.getClassLoader(),
new Class[] { Star.class },
handler
);
// 4. 使用代理对象调用方法
proxy.sing("只因你太美");
System.out.println("----------------------");
proxy.dance();
System.out.println("----------------------");
proxy.rap();
}
}
(4)运行结果
bash
经纪人准备安排行程...
吉哥正在唱只因你太美
经纪人处理后事务...
----------------------
经纪人准备安排行程...
吉哥正在跳舞
经纪人处理后事务...
----------------------
经纪人准备安排行程...
吉哥正在说唱
经纪人处理后事务...
通过使用Java的动态代理机制,我们成功为大明星添加了额外的功能,而不需要修改原始的 BigStar
类。这使得我们可以轻松地在不同场景下扩展类的行为,从而更好地符合开闭原则和单一职责原则。
三、总结
总的来说,动态代理就是允许在运行时创建代理对象,以代替直接访问对象。我们在示例代码中展示了如何使用动态代理来扩展类的行为,通过代理对象在原有行为上添加额外的逻辑,这也是面向切面编程(AOP)的一种常见应用。