Java代理模式之静态代理与动态代理的区别及优缺点
作者:蜀山剑客李沐白
1. 代理模式的定义
代理模式(Proxy Pattern)是指通过代理对象控制对目标对象的访问,并在不改变目标对象的情况下添加额外的功能或控制访问。代理对象和目标对象实现相同的接口,使得客户端可以通过代理对象间接地访问目标对象。
代理模式属于结构型设计模式,它在系统中引入了一个代理对象,该对象代替了客户端直接访问目标对象,从而可以在目标对象的基础上增加一些额外的功能或控制访问。
2. 代理模式的原理
代理模式的核心思想是引入一个代理对象来控制对目标对象的访问。代理对象和目标对象实现相同的接口,使得客户端可以通过代理对象间接地访问目标对象。代理对象负责处理客户端的请求,并在必要时将请求转发给目标对象。在这个过程中,代理对象可以添加额外的逻辑,如权限检查、缓存、日志记录等。
代理模式的主要角色有:
- 抽象主题(Subject):定义了代理对象和目标对象的共同接口,在Java中通常是一个接口或抽象类。
- 目标对象(RealSubject):定义了代理对象所代表的真实对象,是业务逻辑的具体执行者。
- 代理对象(Proxy):持有对目标对象的引用,并实现了与目标对象相同的接口,在方法调用前后进行额外操作。
代理模式的工作流程如下:
- 客户端向代理对象发送请求。
- 代理对象收到请求后,可以在请求被转发给目标对象之前或之后执行一些额外的逻辑。
- 代理对象将请求转发给目标对象。
- 目标对象执行具体的业务逻辑并返回结果。
- 代理对象可以在目标对象返回结果之前或之后执行一些额外的操作。
- 代理对象将结果返回给客户端。
通过引入代理对象,代理模式可以控制对目标对象的访问,并在不改变目标对象的情况下添加额外的功能或控制访问。
3. 代理模式的实现方式
在Java中,代理模式主要有两种实现方式:静态代理和动态代理。
3.1 静态代理
静态代理是指在编译时就已经确定了代理对象和目标对象的关系,代理类是通过手动编写代码来实现的。在静态代理中,代理类和目标类都实现相同的接口,代理类持有目标对象,并在方法调用前后进行额外的操作。
静态代理的工作原理如下:
- 定义一个接口(或抽象类)作为目标接口,目标对象实现这个接口。
- 创建一个代理类,实现目标接口,并持有目标对象的引用。
- 在代理类中重写目标接口的方法,在方法调用前后执行需要的额外操作。
- 客户端使用代理对象来访问目标对象。
静态代理的特点:
- 需要手动编写代理类,工作量较大。
- 目标对象必须实现接口。
- 代理类和目标类的关系在编译时就确定了,无法动态改变。
静态代理的应用场景:
- 安全控制:代理类可以在调用目标方法前进行权限检查等安全控制。
- 远程调用:代理类可以封装网络通信相关的细节,使得调用远程方法就像调用本地方法一样简单。
- 性能监控:代理类可以收集方法的执行时间、调用次数等信息,用于性能监控和统计分析。
下面是一个简单的静态代理的示例代码:
// 定义接口 public interface Image { void display(); } // 目标类 public class RealImage implements Image { private String filename; public RealImage(String filename) { this.filename = filename; loadFromDisk(); } private void loadFromDisk() { System.out.println("Loading image: " + filename); } @Override public void display() { System.out.println("Displaying image: " + filename); } } // 代理类 public class ImageProxy implements Image { private String filename; private RealImage realImage; public ImageProxy(String filename) { this.filename = filename; } @Override public void display() { if (realImage == null) { realImage = new RealImage(filename); } realImage.display(); } } // 测试类 public class ProxyPatternDemo { public static void main(String[] args) { // 使用代理对象显示图片 Image image = new ImageProxy("test.jpg"); image.display(); // 图片已加载,直接显示,无需重新加载 image.display(); } }
在上面的示例中,Image
是一个接口,RealImage
是目标类,负责加载和显示图片。ImageProxy
是代理类,通过持有目标类的引用,在需要时创建并使用目标类。ProxyPatternDemo
是一个测试类,用于演示静态代理的使用。
在测试类中,首先创建一个代理对象 ImageProxy
,并调用 display()
方法显示图片。代理对象会在第一次调用 display()
方法时,创建真实对象 RealImage
并调用其 display()
方法加载和显示图片。在后续调用 display()
方法时,代理对象直接调用真实对象的 display()
方法,无需重新加载图片。
静态代理的缺点是需要手动编写代理类,工作量较大。如果接口中的方法较多或频繁变动,就需要频繁修改代理类的代码,增加了维护的难度。此外,静态代理的代理类和目标类之间存在紧耦合关系,一旦目标类发生变化,代理类也需要相应修改。
为了解决静态代理的缺点,Java还提供了动态代理机制。
3.2 动态代理
动态代理是指在运行时生成代理对象,而无需手动编写代理类。Java的动态代理机制是基于反射实现的,通过使用java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口来实现动态代理。
在动态代理中,代理类的创建和方法调用都是在运行时完成的。代理对象是在内存中动态创建的,并实现了目标对象的接口,同时持有目标对象的引用。在方法调用时,代理对象通过调用InvocationHandler
接口中的方法来处理请求,可以在方法调用前后执行额外的操作。
动态代理的工作原理如下:
- 定义一个接口,作为目标接口。
- 创建一个
InvocationHandler
接口的实现类,该类负责处理方法调用并执行额外的操作。 - 使用
Proxy
类的静态方法newProxyInstance()
生成代理对象,同时指定目标对象和InvocationHandler
。 - 客户端使用代理对象来访问目标对象的方法。
动态代理的特点:
- 不需要手动编写代理类,代理对象在运行时动态生成。
- 目标对象可以不实现接口,只需定义目标对象的共同接口。
- 代理对象和目标对象的关系在运行时确定,可以动态改变。
动态代理的应用场景:
- AOP(面向切面编程):动态代理可以在目标方法执行前后执行额外的操作,如权限检查、事务管理等。它是实现AOP的一种常见方式。
- 延迟加载:动态代理可以在目标方法被调用时才进行加载和初始化,实现延迟加载的效果。
- 日志记录:动态代理可以在目标方法执行前后记录日志信息。
下面是一个简单的动态代理的示例代码:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; // 定义接口 interface Image { void display(); } // 目标类 class RealImage implements Image { private String filename; public RealImage(String filename) { this.filename = filename; loadFromDisk(); } private void loadFromDisk() { System.out.println("Loading image: " + filename); } @Override public void display() { System.out.println("Displaying image: " + filename); } } // InvocationHandler 实现类 class ImageInvocationHandler implements InvocationHandler { private Object target; public ImageInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before method: " + method.getName()); Object result = method.invoke(target, args); System.out.println("After method: " + method.getName()); return result; } } // 测试类 public class ProxyPatternDemo { public static void main(String[] args) { // 创建目标对象 Image realImage = new RealImage("test.jpg"); // 创建 InvocationHandler 实例 ImageInvocationHandler handler = new ImageInvocationHandler(realImage); // 创建代理对象 Image imageProxy = (Image) Proxy.newProxyInstance(Image.class.getClassLoader(), new Class[]{Image.class}, handler); // 使用代理对象显示图片 imageProxy.display(); } }
在上面的示例中,Image
是一个接口,RealImage
是目标类,负责加载和显示图片。ImageInvocationHandler
是InvocationHandler
接口的实现类,用于处理方法调用并执行额外的操作。ProxyPatternDemo
是一个测试类,用于演示动态代理的使用。
在测试类中,首先创建一个目标对象 RealImage
,然后创建一个 ImageInvocationHandler
实例,并将目标对象传入构造函数。接下来,通过调用 Proxy
类的 newProxyInstance()
方法来生成代理对象。最后,使用代理对象调用 display()
方法显示图片。
在方法调用时,代理对象会调用 InvocationHandler
接口中的 invoke()
方法处理方法调用。在示例中,我们在 invoke()
方法中实现了打印方法名的额外操作,并通过反射调用目标对象的方法。
以上就是Java代理模式之静态代理与动态代理的区别及优缺点的详细内容,更多关于Java 静态与动态代理的资料请关注脚本之家其它相关文章!