java代理模式
java代理模式
- 代理模式
-
- 静态代理
-
- 代码示例与详解
- 动态代理
-
- 代码示例与详解
代理模式
代理模式是一种程序设计模式,允许通过代理对象控制对目标对象的引用。Java 代理的核心思想是通过代理对象控制对目标对象的访问,并在不修改目标对象代码的前提下增强其功能。
代理模式存在的原因:有一些核心对象是不能交给用户直接进行访问,因为用户传输过来的数据没有经过认证,要考虑它是否安全,如果直接访问当前核心对象是否可行,核心对象是否能够处理用户直接传的这些数据等等。为了解决问题,我们在当前用户对象和核心对象之间创建一个代理对象,这个代理对象控制着对核心对象的引用,用户只能去访问当前的代理对象,代理对象觉得当前传输过来的数据没问题后,再去访问当前核心对象,最后数据再通过当前代理对象反馈给用户。
我们来画图演示代理模式:
现实中小明不能直接调用到目标对象,代理模式可以让小明调用到当前代理对象。目标类生成目标对象,目标对象里边有提供好的核心方法,代理类生成代理对象,代理对象控制对目标对象的引用。那么存在这样一个问题:代理对象是怎么知道目标对象的核心方法是什么呢?目标类对外提供的是一个接口,在这个接口里定义着核心方法,当代理类对接口进行实现的时候,相当于是接口对代理类做了通知,告诉当前代理类要代理的核心方法是什么。通过这么一种间接的形式通知了代理对象目标对象里边所需要代理的核心方法是什么。
代理的目的:
- 功能增强:通过代理业务对原有业务进行增强
- 控制访问:通过代理对象的方式间接访问目标对象,防止我们直接访问目标对象给整个系统带来不必要的复杂性
在 mybatis、spring 中底层源码均用到了代理模式
代理模式分为静态代理和动态代理
静态代理
静态代理是指在编译期手动编写代理类,代理类与目标类实现相同的接口,通过显式持有目标对象的引用,在调用目标方法前后添加增强逻辑。
Oracle 的 Java 官方文档未直接描述静态代理,其核心原因在于:静态代理是设计模式的实现方式,而非 Java 语言特性或 API,而动态代理由 Java 反射机制直接支持,更符合 Oracle 文档对技术实现的聚焦。
代码示例与详解
我们来举例说明静态代理的实现:
该代码以制造衣服和鞋子为例,有一个造衣服工厂 ClothesFactory 和一个制鞋工厂 ShootFactory,造衣服工厂里有一个定制衣服 makeClothes(Integer size) 方法,输出“为您定制了一款大小为 size 的衣服”,制鞋工厂里边有一个制鞋 shoot(Integer size) 方法,输出“为您定制了一款大小为 size 的鞋子”。
此时小明想去买衣服买鞋,我们创建一个类叫做 Xiaoming,小明能直接去造衣服和造鞋工厂里边买衣服买鞋吗?不行,他只能去找代理买衣服买鞋。那么我们创建一个类 Proxy1 代理来代理工厂,代理工厂需要创建 ClothesFactory 和 ShootFactory 两个工厂的代理对象,这也就是实现了代理的一个目的控制访问。代理可以代理工厂但是代理是没办法知道工厂里边到底是怎么做衣服的,所以两个工厂分别对外提供了 makeClothes 和 ByShoot 接口。接口的作用是做通知,告诉代理类所代理的工厂里的核心方法是什么,代理类和工厂都需要去实现这个接口,实现接口就意味着要实现接口当中的核心方法。代理对象实现两个工厂提供的接口,分别在实现接口的方法中调用对应代理对象的 makeClothes 和 shoot 方法,并且需要将 size 参数传进去,同时我们可以进行方法增强,添加前置服务和后置服务,也就是实现了代理的另一个目的功能增强。对于小明来说他不能直接调用工厂的方法,所能调用到的只能是 Proxy1 代理对象,所以小明想去买衣服买鞋就需要先创建 Proxy1 对象,调用 Proxy1 的 makeClothes 和 shoot 方法,运行这个方法会得到相应的输出如图。
总结静态代理是如何实现的:
- 第一步是创建目标类的对象
- 第二步是实现目标类的接口,用于获取当前这个目标类核心功能是什么
- 第三步功能增强
静态代理的问题:
目标对象不断增多,而代理对象只有一个,一个代理对象控制着多个目标对象。当代理多个目标对象的时候,如果每新增一个,就要对原有代码进行改动。这违反了程序的开放封闭原则,软件实体应该对扩展开放,对修改封闭,即通过扩展添加新功能,而非修改现有代码,降低风险。
所以我们更多的是使用动态代理模式。
动态代理
Oracle java document 中对动态代理的解释如下:
A dynamic proxy class is a class that implements a list of interfaces specified at runtime such that a method invocation through one of the interfaces on an instance of the class will be encoded and dispatched to another object through a uniform interface. Thus, a dynamic proxy class can be used to create a type-safe proxy object for a list of interfaces without requiring pre-generation of the proxy class, such as with compile-time tools. Method invocations on an instance of a dynamic proxy class are dispatched to a single method in the instance’s invocation handler, and they are encoded with a object identifying the method that was invoked and an array of type containing the arguments.java.lang.reflect.MethodObject
Java 动态代理是一种在程序运行时动态创建代理类和代理对象的技术机制,它允许开发者在不修改原始类代码的前提下,通过代理对象来增强或改变目标对象的行为。
Java 动态代理主流实现方式包括 JDK 动态代理和 CGLIB 动态代理。
而 JDK 动态代理是 Java 原生支持的动态代理方式,通过 java.lang.reflect.Proxy 类和 InvocationHandler 接口实现,要求目标类必须实现接口,代理类在运行时动态生成并实现相同接口。
JDK 的动态代理过程:每新增一个目标类目标对象,就有一个当前代理对象来控制对目标对象的引用,存在着这样一对一的关系,类创建对象是自主的过程,如图。
代码示例与详解
我们还是使用制造衣服和鞋子的例子来实现动态代理
输出结果如下:
动态代理和静态代理的实现步骤是一样的,下面进行具体代码解释:
第一步需要完成对目标类的创建
Proxy2 类实现了InvocationHandler 接口,InvocationHandle 是 jdk 提供的一个接口帮助实现动态代理,该接口的作用是定义代理逻辑。
private Object target;public Proxy2(Object target) {this.target = target;}
private Object target;
定义一个目标对象的变量,Object 是 Java 中所有类的父类,用 Object 类型声明变量 target,意味着它可以接收任何类型的对象,private 修饰表示这个变量是 Proxy2 类的私有成员,只能在内部使用外部无法直接访问。public Proxy2(Object target) { this.target = target; }
通过构造方法初始化目标对象,作用是在创建 Proxy2 实例时,接收外部传入的目标对象,并把它存到上面定义的 target 变量中。
Proxy2 d = new Proxy2(new ClothesFactory());
new ClothesFactory() 就是创建这个衣服工厂的实例,创建的衣服工厂对象会作为参数传给构造方法,把传入的目标对象保存到 Proxy2 自己的 target 变量中,并且 Proxy2 是实现了 InvocationHandler 接口的代理。那么这句代码的作用是创建一个代理对象,并告诉它要代理哪个目标对象,即代理对象控制着对核心对象的引用。
这样就实现了每新增一个目标对象就能生成一个代理对象对其进行代理,堆栈内存图如下。
第二步实现目标类的接口
makeClothes makeClothes = (com.qcby.makeClothes) d.getProxy();
声明一个符合makeClothes接口规范的变量,用来存放能提供做衣服服务对象的接口,d.getProxy() 方法返回的是 Object 类型,而我们需要明确它是 makeClothes 接口类型,动态生成的类强制实现了makeClothes接口,只要它实现了makeClothes接口,就一定能调用makeClothes()方法。
这里我们需要注意,这里并不是接口在创建实例,而是接口的实现类的实例。 Java 允许用接口作为变量的类型,但这个变量实际指向的是实现了该接口的类的实例,而不是接口本身的实例,所以它是接口的实现类的实例,而不是接口的实例。
具体来看 getProxy() 方法:
public Object getProxy(){return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);}
其中 Proxy.newProxyInstance(…) 是 Java 官方提供的动态生成类的工具,能在程序运行时自动创建一个全新的代理类和代理对象,而这个类在你的代码里是找不到.java或.class文件的,完全是内存中临时生成的。
这个方法需要三个参数:target.getClass().getClassLoader()
作用是获取加载当前对象所属类的类加载器,和目标对象用同一个类加载器,用于加载代理类;target.getClass().getInterfaces()
作用是获取当前对象所属类直接实现的所有接口的 Class 对象数组;this
指的是当前 Proxy2 实例,告诉新生成的代理对象,当有人调用你的方法时,把这个调用交给当前 Proxy2 实例处理,触发 Proxy2 的 invoke() 方法,所以调用代理对象的makeClothes(100)时,会自动执行 invoke() 方法,你可以在 invoke() 方法中自定义处理逻辑。
第三步功能增强
invoke 这个方法是 jdk 提供的,帮助调用目标对象的核心方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {FrontService(); method.invoke(target,args); endService();return null;}
其中 method 是目标对象的核心方法,即通过代理对象实际调用的那个方法,args 是目标对象核心方法参数
makeClothes.makeClothes(100);
调用代理对象的方法,这一步不会直接调用 ClothesFactory 的 makeClothes 方法,而是先进入 Proxy2 内部关联的 InvocationHandler 的 invoke 方法,三个参数分别是:proxy 就是 d.getProxy() 返回的那个代理对象,method 代表makeClothes接口中的makeClothes(int)方法,args 调用时传入的参数数组[100]。在 invoke 方法中,会执行增强逻辑,然后再通过 method.invoke(target, args) 反射调用 ClothesFactory 真正的 makeClothes(100) 方法。
表面现象:变量makeClothes是makeClothes接口类型,调用它的makeClothes(100)方法,看起来和直接调用ClothesFactory的makeClothes方法一样。
背后真相:这里的makeClothes变量指向的是代理对象,所以调用makeClothes(100)时,不会直接执行ClothesFactory的方法,而是会被Proxy2的invoke方法 “拦截”,从而在执行目标方法前后自动加上增强逻辑。也就是说,当调用proxyObj.makeClothes(100)时,实际执行的是:市场调研 → 做衣服(目标方法) → 包办售后
总结:Proxy2是一个 “万能增强器”,通过它生成的代理对象,能在不修改原工厂代码的情况下,给所有工厂的核心方法自动加上 “市场调研” 和 “包办服务”,这就是动态代理的作用。