代理模式(Proxy Pattern)是一种结构型设计模式,它的核心思想是通过代理对象来控制对目标对象的访问。代理对象充当目标对象的代表,客户端实际访问的是代理对象,而不是直接访问目标对象。这种模式在软件开发中有着广泛的应用,特别是在需要对对象访问进行控制、增强或保护的场景。
1. 代理模式的结构
代理模式通常涉及以下三个角色:
- 抽象主题(Subject) :定义了目标对象和代理对象的共同接口,这样在任何使用目标对象的地方都可以使用代理对象。它可以是一个接口或抽象类。例如,在一个图形绘制系统中,可能定义一个
Shape接口作为抽象主题,包含draw方法。
java
public interface Shape {
void draw();
}
- 目标对象(RealSubject) :实现了抽象主题接口,是实际执行操作的对象。比如,具体的
Circle类实现了Shape接口,是目标对象。
java
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
- 代理对象(Proxy) :同样实现了抽象主题接口,内部持有一个目标对象的引用。代理对象在客户端调用时,可以在调用目标对象的方法前后添加额外的逻辑,如访问控制、日志记录等。例如,
CircleProxy类就是代理对象。
java
public class CircleProxy implements Shape {
private Circle circle;
public CircleProxy() {
this.circle = new Circle();
}
@Override
public void draw() {
System.out.println("Before drawing a circle");
circle.draw();
System.out.println("After drawing a circle");
}
}
2. 代理模式的工作原理
客户端通过代理对象来访问目标对象。当客户端调用代理对象的方法时,代理对象可以在调用目标对象的方法之前或之后执行一些额外的操作,然后再将调用转发给目标对象。例如,在上述图形绘制系统中,客户端代码如下:
java
public class Client {
public static void main(String[] args) {
Shape circleProxy = new CircleProxy();
circleProxy.draw();
}
}
在这个例子中,客户端创建了 CircleProxy 代理对象并调用其 draw 方法。CircleProxy 在调用 Circle 的 draw 方法前后分别输出了一些信息,实现了对目标对象访问的增强。
3. 代理模式的类型
- 静态代理 :代理类在编译时就已经确定,是手动编写的。如上述
CircleProxy类就是静态代理的例子,它明确地实现了Shape接口,并在内部持有Circle对象的引用。静态代理的优点是实现简单,容易理解;缺点是如果目标对象的接口方法很多,代理类的代码会变得冗长,并且当目标对象的接口发生变化时,代理类也需要相应地修改。 - 动态代理 :代理类是在运行时动态生成的。Java提供了两种动态代理方式:JDK动态代理和CGLIB动态代理。
- JDK动态代理 :利用Java反射机制在运行时创建代理类。它要求目标对象必须实现一个接口,通过
Proxy类和InvocationHandler接口来创建代理对象。例如:
- JDK动态代理 :利用Java反射机制在运行时创建代理类。它要求目标对象必须实现一个接口,通过
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKProxyExample {
public static void main(String[] args) {
Circle target = new Circle();
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method invocation");
Object result = method.invoke(target, args);
System.out.println("After method invocation");
return result;
}
};
Shape proxy = (Shape) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler);
proxy.draw();
}
}
上述JDk动态代理代码也可分开始
1.先创建动态代理类
java
public class JDKDynamicProxy implements InvocationHandler {
private Object target;
public JDKDynamicProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before invoke method");
Object result = method.invoke(target, args);
System.out.println("after invoke method");
return result;
}
}
2.创建工厂类
java
public class JDKProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new JDKDynamicProxy(target)
);
}
}
3.测试
java
public class JDKProxyTest {
public static void main(String[] args) {
Shape proxy = (Shape)JDKProxyFactory.getProxy(new Circle());
proxy.draw();
}
}
- CGLIB动态代理:通过字节码生成技术,在运行时为目标类创建一个子类作为代理类。它不需要目标对象实现接口,而是通过继承目标类来创建代理。CGLIB动态代理性能较高,适用于没有实现接口的类。例如:
java
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLIBProxyExample {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Circle.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method invocation");
Object result = proxy.invokeSuper(obj, args);
System.out.println("After method invocation");
return result;
}
});
Circle proxy = (Circle) enhancer.create();
proxy.draw();
}
}
4. 代理模式的适用场景
- 远程代理:当需要访问远程对象时,使用代理对象来代表远程对象进行访问。例如,在分布式系统中,客户端通过代理对象与远程服务器上的对象进行通信,代理对象负责处理网络连接、序列化/反序列化等细节。
- 虚拟代理:对于一些创建开销较大的对象,使用代理对象来延迟对象的创建。比如,在一个图像查看器中,当打开一个包含大量高清图片的文件夹时,开始只显示图片的缩略图(代理对象),只有当用户点击查看某张图片时,才真正加载高清图片(目标对象)。
- 保护代理:用于控制对目标对象的访问权限。例如,在一个多用户系统中,不同用户对某些资源的访问权限不同,代理对象可以在调用目标对象的方法前,检查当前用户的权限,决定是否允许访问。
- 缓存代理:在代理对象中缓存目标对象的操作结果,以提高性能。比如,对于一些频繁调用且结果不经常变化的方法,代理对象可以缓存方法的返回值,下次相同调用时直接返回缓存结果,而不需要再次调用目标对象。
代理模式通过引入代理对象,有效地将客户端与目标对象分离,增强了系统的灵活性和可维护性,同时提供了对目标对象访问的控制和扩展能力。
| 比较项 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 实现原理 | 利用Java反射机制,在运行时创建实现了目标接口的代理类,通过InvocationHandler回调机制处理方法调用 |
通过ASM字节码生成库,在运行时创建目标类的子类作为代理类,重写目标类方法并借助MethodProxy调用目标方法 |
| 代理目标要求 | 目标对象必须实现至少一个接口 | 目标对象可以是普通类,无需实现接口 |
| 性能表现 | 代理对象创建速度快,但方法调用通过反射,存在一定性能开销 | 代理对象创建速度慢,因字节码生成较复杂;方法调用直接,性能相对较高 |
| 应用场景 | 适用于目标对象已实现接口,且对代理对象创建速度要求较高的场景,如RMI等框架 | 适用于目标对象未实现接口,或对代理对象方法调用性能要求较高的场景,如Spring AOP处理无接口目标对象 |
| 代码实现 | 主要依赖Proxy类和InvocationHandler接口,需实现InvocationHandler并重写invoke方法,通过Proxy.newProxyInstance创建代理对象 |
依赖CGLIB库的Enhancer类和MethodInterceptor接口,实现MethodInterceptor并重写intercept方法,借助Enhancer设置目标类、回调函数来创建代理对象 |
| 代理类与目标对象关系 | 代理类实现与目标对象相同的接口,持有目标对象引用 | 代理类继承目标类,通过继承获取目标对象的方法 |
| 对类成员的代理支持 | 仅能代理接口中定义的方法 | 可代理目标类的所有非final方法,包括private方法(通过反射突破访问限制) |
| 对构造函数的处理 | 不涉及目标类构造函数,通过接口调用目标对象方法 | 可能会影响目标类构造函数调用,因为代理类是目标类的子类,创建代理对象时可能触发目标类构造函数 |