相信我们在网上和平时学习和工作中或多或少都接触过 Java 的代理模式, 经常听到什么静态代理, 动态代理的一些名词. 但我们是否真的很清楚这些呢? 至少我在面试时, 发现很多人并不很清楚.
首先代理比较好理解, 就是帮一个人, 或者一类人做一些事情. 迁移到面向对象的程序设计中, 代理就是帮一个类去做一些事情, 而这个代理的工具我们就称为代理类.
通过代理的方式去做事有什么好处呢? 这就好比工厂和分销商做的事情一样, 工厂可以直卖一些自己的产品, 分销商同样也可以卖工厂生产的产品, 那么为什么还有分销商的存在呢? 因为分销商可以提供一些额外的服务, 或者在销售的过程中能够完成一些其他的事情, 比如组合销售, 根据本地情况做活动等, 而这些可能是工厂不想关心或者也管不过来的. 这样的功能和角色承包给代理商就会使得分工比较明晰, 并且又能够提供一些额外或者定制的服务.
静态代理
Java 中的代理方式可以分为静态代理和动态代理. 静态代理的含义是代理类 / 对象在我们关心的程序运行前就已经确定或存在. 静态代理比较好理解, 我们在日常工作中也是经常用到, 比如一个已经存在的接口, 我们不期望去更改它, 但是现在要在原逻辑上新加一些逻辑或功能, 比如原接口方法调用完成后发送一个消息之类的. 于是我们可以创建一个类, 同样实现原接口, 并且把之前存在的接口当做成员变量注入进来, 调用其中的方法, 并添加我们需要的功能.
静态代理的类图如下所示, 需要被代理的实现类和代理类都实现了抽象接口 AbstractInterface, 而 InterfaceProxy 和 InterfaceImpl 间是聚合关系.
来看一段示例代码, ProductAuditCallbackService 是我们已有的一个接口, 出于某些原因, 这个接口不能继续对外使用, 我们需要定义一个新的接口并且名称还要一样 (主要是方便客户理解和对应原接口), 但是我们需要添加一点 "新逻辑". 因此我们可以同样实现 ProductAuditCallbackService,ProductAuditCallbackServiceProxy 就是我们的代理类, 之后外部调用就可以实例化我们的代理类, 调用同名方法就好了.
- public class ProductAuditCallbackServiceProxy implements ProductAuditCallbackService {
- @Resource
- private ProductAuditCallbackService productAuditCallbackService;
- @Override
- public Result<Void> auditProduct(ProductAuditRequest request, String auditStatus) {
- if (auditStatus == "DELETED") {
- return new Result<>();
- }
- return productAuditCallbackService.auditProduct(request, auditStatus);
- }
- ...
- }
动态代理
动态代理的作用和静态代理一样, 主要的区别就在于需要在运行时生成代理类. 在使用动态代理时, 我们还需要定义一个在代理类和委托类之间的中介类, 并且中介类需要实现 java.lang.reflect.InvocationHandler 接口.
- package java.lang.reflect;
- /**
- * {@code InvocationHandler} is the interface implemented by
- * the <i>invocation handler</i> of a proxy instance.
- *
- * <p>Each proxy instance has an associated invocation handler.
- * When a method is invoked on a proxy instance, the method
- * invocation is encoded and dispatched to the {@code invoke}
- * method of its invocation handler.
- *
- * @author Peter Jones
- * @see Proxy
- * @since 1.3
- */
- public interface InvocationHandler {
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable;
- }
动态代理在框架类的代码中用到的频率并不低, 而且能够使我们的代码看起来更高级一些, 所以何乐而不为呢? 让我们来看一些实际的例子.
MethodInvocationHandler 是一个中介类, 实现了 InvocationHandler 接口, MethodMonitor 这个类的功能就是要统计我们的委托类的对象 business 中的方法被调用的次数和耗时, 由于其主要功能不是我们关注的主要内容, 所以忽略其实现.
- public class MethodInvocationHandler implements InvocationHandler {
- // 被代理对象
- private Object business;
- private final MethodMonitor methodMonitor;
- public MethodInvocationHandler(MethodMonitor methodMonitor) {
- this.methodMonitor = methodMonitor;
- }
- /**
- * 代理方法
- */
- @Override
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- long startTime = System.currentTimeMillis();
- Object result = method.invoke(this.business, args);
- // 方法调用统计
- this.methodMonitor.methodCount(this.business.getClass().getSimpleName() + POINT + method.getName(), startTime);
- return result;
- }
- }
其余示例代码及外部调用示例如下, 我们的 Business 类里面拥有三个方法. MethodSampleClient 则是一个封装起来的客户端. 我们不想让外部客户端感知我们的实现以及和 Business 的关系, 于是我们在 MethodSampleClient 中定义了一个成员变量 proxy, 当外部需要 Business 提供的一些功能时, 我们通过 proxy 为其提供. Proxy.newProxyInstance() 则是我们实例化一个代理类的方式, 哟, 这还是个工厂模式, 可以阅读一些这个方法的说明, 需要传入的三个参数依次是: 需要被代理的类的 ClassLoader, 被代理类需要被代理的接口的集合, 中介处理类的实例.
这里 Business 我写的是一个确定的类, 其实真正在实际开发工作中, 我们往往定义的抽象的接口或抽象类, 知道运行时才会确定到底是哪个实现类的实例, 这样可能更容易理解一些: 运行时确定委托类的实现类, 运行时生成代理类, 并调用对应的委托类的方法.
- public class Business {
- public void createJob() {
- System.out.println("test createJob");
- }
- public void processJob() {
- System.out.println("test processJob");
- }
- public void closeJob() {
- System.out.println("test closeJob");
- }
- }
- public class MethodSampleClient {
- private Business business;
- @Getter
- private Object proxy;
- private InvocationHandler invocationHandler;
- public void init() {
- this.business = new Business();
- this.invocationHandler = new MethodInvocationHandler(new MethodMonitor());
- this.proxy = bind(this.business, invocationHandler);
- }
- /**
- * 绑定对象, 直接初始化并返回代理类供客户端使用
- */
- public Object bind(Object business, InvocationHandler invocationHandler) {
- return Proxy.newProxyInstance(
- // 被代理类的 ClassLoader
- business.getClass().getClassLoader(),
- // 要被代理的接口, 本方法返回对象会自动声称实现了这些接口
- business.getClass().getInterfaces(),
- // 代理处理器对象
- invocationHandler);
- }
- }
- /**
- * A simple client test class
- */
- public class Test {
- public void main(String[] args) {
- MethodSampleClient methodSampleClient = new MethodSampleClient();
- methodSampleClient.init();
- methodSampleClient.getProxy().createJob();
- methodSampleClient.getProxy().processJob();
- methodSampleClient.getProxy().closeJob();
- }
- }
为了说清楚这个过程, 竟然还真的写了不少代码, 看起来比较繁琐. 总结一下, 动态代理无非按照下面的步骤来编写代码:
首先明确需要被代理的委托类.
实现 InvocationHandler 接口, 定义一个中介类.
用 Proxy.newProxyInstance() 实例化代理类, 并在客户端代码中直接使用.
好了, 大概差不多了, 最重要的是能够在实际工作中有意识地去使用并体会其作用 -- 软件开发是经验驱动不是知识驱动.
来源: https://www.cnblogs.com/XiaoHDeBlog/p/12996876.html