动态代理
动态代理是一种在运行时创建代理对象的技术,它能够在不修改目标对象源代码的情况下,对目标对象的方法调用进行增强或控制。以下从几个方面详细介绍动态代理:
1. 基本概念
-
代理对象:动态代理会创建一个代理对象,这个代理对象会代替目标对象接收外部的方法调用。它看起来和目标对象具有相同的接口,外部调用者可以像调用目标对象一样调用代理对象的方法。
-
目标对象:也就是被代理的实际对象,它包含了真正要执行的业务逻辑。代理对象最终会将接收到的方法调用转发给目标对象去执行。
2. 工作原理
-
当外部调用者调用代理对象的某一方法时,代理对象首先会拦截这个方法调用。然后,它可以根据具体需求进行一些前置处理,比如进行权限验证、日志记录等操作。
-
完成前置处理后,代理对象会将方法调用转发给目标对象,让目标对象执行真正的业务逻辑。
-
在目标对象执行完业务逻辑返回结果后,代理对象还可以进行一些后置处理,比如对返回结果进行加工、统计方法执行时间等操作,最后再将处理后的结果返回给外部调用者。
3. 实现方式(以 Java 为例)
Java 中有两种常见的实现动态代理的方式:基于接口的动态代理(使用 Java 反射机制和java.lang.reflect.Proxy
类)和基于类的动态代理(使用第三方库如 CGLIB)。
基于接口的动态代理:
-
要求目标对象必须实现一个或多个接口。
-
示例代码如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义接口
interface HelloWorld {
void sayHello();
}
// 实现接口的目标对象
class HelloWorldImpl implements HelloWorld {
@Override
public void sayHello() {
System.out.println("Hello, World!");
}
}
// 实现InvocationHandler接口,用于处理代理对象的方法调用
class MyInvocationHandler implements InvocationHandler {
private final Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置处理,比如记录日志
System.out.println("Before method call: " + method.getName());
// 调用目标对象的方法并获取结果
Object result = method.invoke(target, args);
// 后置处理,比如统计方法执行时间
System.out.println("After method call: " + method.getName());
return result;
}
}
public class DynamicProxyExample {
public static void main(String[] args) {
// 创建目标对象
HelloWorld helloWorld = new HelloWorldImpl();
// 创建InvocationHandler对象
MyInvocationHandler handler = new MyInvocationHandler(helloWorld);
// 创建代理对象
HelloWorld proxy = (HelloWorld) Proxy.createProxyInstance(HelloWorld.class.getClassLoader(),
new Class[]{HelloWorld.class}, handler);
// 调用代理对象的方法
proxy.sayHello();
}
}
在上述代码中:
-
首先定义了
HelloWorld
接口和实现该接口的HelloWorldImpl
目标对象。 -
然后创建了
MyInvocationHandler
类,它实现了InvocationHandler
接口,用于处理代理对象的方法调用。在invoke
方法中实现了前置处理、调用目标对象方法以及后置处理的功能。 -
最后通过
Proxy.createProxyInstance
方法创建了代理对象,并调用代理对象的方法,此时可以看到前置处理和后置处理的效果。
基于类的动态代理(以 CGLIB 为例):
-
不需要目标对象实现接口,它可以直接代理类。
-
示例代码如下:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
// 目标对象,没有实现任何接口
class HelloWorld {
public void sayHello() {
System.out.println("Hello, World!");
}
}
// 实现MethodInterceptor接口,用于处理代理对象的方法调用
class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 前置处理,比如记录日志
System.out.println("Before method call: " + method.getName());
// 调用目标对象的方法并获取结果
Object result = proxy.invokeSuper(obj, args);
// 后置处理,比如统计方法执行时间
System.out.println("After method call: " + method.getName());
return result;
}
}
public class CglibDynamicProxyExample {
public static void main(String[] args) {
// 创建目标对象
HelloWorld helloWorld = new HelloWorld();
// 创建Enhancer对象
Enhancer enhancer = new Enhancer();
// 设置目标对象的类
enhancer.setTarget(helloWorld);
// 设置MethodInterceptor
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理对象
HelloWorld proxy = (HelloWorld) enhancer.create();
// 调用代理对象的方法
proxy.sayHello();
}
}
在上述代码中:
-
定义了没有实现任何接口的
HelloWorld
目标对象。 -
然后创建了
MyMethodInterceptor
类,它实现了MethodInterceptor
接口,用于处理代理对象的方法调用。在intercept
方法中实现了前置处理、调用目标对象方法以及后置处理的功能。 -
最后通过
Enhancer
创建了代理对象,并调用代理对象的方法,同样可以看到前置处理和后置处理的效果。
4. 应用场景
-
权限控制:在企业级应用中,对于不同用户访问某些敏感业务方法时,可以通过动态代理在方法调用前进行权限验证,只有具备相应权限的用户才能访问到真正的业务方法。
-
日志记录:为了方便跟踪和分析应用程序的运行情况,可以在代理对象的方法调用前后分别进行日志记录,记录下方法的名称、参数、执行时间等信息,以便于后续的故障排查和性能分析。
-
性能监控:通过动态代理可以统计目标对象的方法执行时间,以此来评估业务方法的执行效率,及时发现可能存在的性能问题并进行优化。
动态代理是一种非常有用的技术,它为软件开发提供了一种灵活的方式来对目标对象的方法调用进行增强和控制,在许多应用场景中都发挥着重要作用。