写点什么

06. 动态代理设计模式

作者:杨充
  • 2024-10-30
    湖北
  • 本文字数:8520 字

    阅读完需:约 28 分钟

06.动态代理设计模式

目录介绍

  • 01.为何要动态代理

  • 1.1 为何要动态代理

  • 1.2 动态代理思考

  • 02.动态代理的概念

  • 2.1 动态代理定义

  • 2.2 动态代理类比理解

  • 2.3 动态代理参与者

  • 2.4 动态代理步骤

  • 03.动态代理的实现

  • 3.1 罗列一个场景

  • 3.2 用一个例子理解代理

  • 3.3 基于接口动态代理

  • 3.4 基于类动态代理

  • 3.5 动态代理模版代码

  • 04.动态代理案例

  • 4.1 动态代理和反射

  • 4.2 Java 中代理

  • 4.3 Retrofit 核心思想

  • 05.动态代理思想和原理

  • 5.1 动态代理实现机制

  • 5.2 动态代理思想

  • 5.3 动态代理存在的问题

  • 5.4 动态代理的优势

  • 06.动态代理总结

  • 6.1 总结一下学习

  • 6.2 更多内容推荐

推荐一个好玩网站

一个最纯粹的技术分享网站,打造精品技术编程专栏!编程进阶网


https://yccoding.com/

01.为何要动态代理

1.0 本博客 AI 摘要

本文详细介绍了动态代理设计模式,包括其必要性、概念、实现方式及案例分析。动态代理允许在运行时动态创建代理对象,增强代码复用性和灵活性,减少类膨胀。文章通过对比静态代理,深入解析了动态代理的实现机制,如基于接口和类的动态代理,以及其在 Retrofit 中的应用。同时,讨论了动态代理的优势和潜在问题,如性能开销和调试难度。最后,提供了丰富的学习资源链接,帮助读者进一步理解和掌握动态代理。

1.1 为何要动态代理

一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色(委托类),该如何使用代理呢?这个问题可以通过 Java 的动态代理类来解决。


  1. 代码复用:动态代理可以通过一个通用的代理类来代理多个类,实现代码的复用。不需要为每个被代理类编写一个代理类,减少了代码的冗余。

  2. 灵活性和扩展性:动态代理在运行时动态地创建代理对象,并可以根据需要动态地添加、修改或删除代理行为。这使得代理行为可以根据不同的需求进行定制和扩展,提供了更大的灵活性和扩展性。

  3. 解耦合:动态代理通过使用接口或基类来代理对象,实现了代理类和被代理类之间的松耦合。客户端只需要与代理对象进行交互,无需关心具体的实现类,提高了代码的可维护性和可扩展性。


总结起来,静态代理在一些简单的场景下可以使用,但在需要更大的灵活性、扩展性和可维护性时,动态代理更为适合,动态代理在解耦方面提供了巨大的便利

1.2 动态代理思考

当涉及到动态代理时,有几个方面值得思考:


  1. 性能考虑:动态代理可能会引入一定的性能开销。在使用动态代理时,需要考虑代理对象的创建和方法调用的性能影响,并权衡是否值得使用代理。

  2. 异常处理:在代理对象中处理异常是很重要的。确定如何处理原始对象方法中可能抛出的异常,并决定是否需要在代理对象中捕获、处理或转发异常。

  3. 安全性考虑:了解代理对象的安全性问题。代理对象可能会暴露原始对象的敏感信息或功能,因此需要确保适当的安全措施。

02.动态代理的概念

2.1 动态代理定义

动态代理的概括


代理类是在运行时生成的。也就是说 Java 编译完之后并没有实际的 class 文件,而是在运行时动态生成的类字节码,并加载到 JVM 中。


动态代理定义


给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象。

2.2 动态代理类比理解

第一个类比:代理对象是中介,原对象是房东的房子,客户就是我们需要找房的人。


静态代理:房东的房子让中介代理出租,中介代理可以直接控制房子出租,用户是不能找房东直接租房子的,而是要间接通过中介租房子。


动态代理:现在中介代理有很多,链家中介,中原地产中介,21 世纪中介,贝壳中介等等,用户在所在区域搜索心仪的房子(公寓,单间,隔断房,床位,整租房等等)后才知道找具体的中介租房子。这就需要用到动态代理!

2.3 动态代理参与者

代理模式的角色分四种:


如下所示


  1. 主题接口:Subject,是委托对象和代理对象都共同实现的接口,即代理类的所实现的行为接口。

  2. 目标对象:ReaSubject,是原对象,也就是被代理的对象。

  3. 代理对象:Proxy,是代理对象,用来封装真是主题类的代理类。

  4. 客户端:使用代理类和主题接口完成一些工作。

2.4 动态代理步骤

定义业务接口和实现:习惯性地首先定义一个接口,然后提供一个或多个实现。


创建调用处理器:实现 InvocationHandler 接口的类,它定义了代理实例的调用处理程序。


生成代理实例:通过调用 Proxy.newProxyInstance(),传入目标类的类加载器、接口数组和调用处理器来创建。

03.动态代理原理和实现

3.1 罗列一个场景

现在有一个需求:用户找中介租房子,假如根据租房的条件,将客户需求分类:比如需要公寓,单间,隔断房,床位,整租房等等。


为其他对象提供一种代理以控制对这个对象的访问。


某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以再两者之间起到中介作用。运行阶段才指定代理哪个对象。


组成元素:


  1. 抽象类接口

  2. 被代理类(具体实现抽象类接口的类)

  3. 动态代理类,实际调用被代理类的方法和属性

3.2 用一个例子理解代理

Java 实现动态代理主要涉及以下几个类:


  1. java.lang.reflect.Proxy: 这是生成代理类的主类,通过 Proxy 类生成的代理类都继承了 Proxy 类,即 DynamicProxyClass extends Proxy。

  2. java.lang.reflect.InvocationHandler: 这里称他为"调用处理器",他是一个接口,我们动态生成的代理类需要完成的具体内容需要自己定义一个类,而这个类必须实现 InvocationHandler 接口。

3.3 基于接口动态代理

基于接口的动态代理是一种常见的动态代理方式,它通过实现 Java 的 Proxy 类和 InvocationHandler 接口来实现。以下是一个基于接口的动态代理的示例:


首先,定义一个接口(例如 MyInterface):


public interface MyInterface {    void doSomething();}
复制代码


然后,创建一个实现 InvocationHandler 接口的代理处理器类(例如 MyInvocationHandler):


public class MyInvocationHandler implements InvocationHandler {    private 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 invocation"); // 调用目标对象的方法 Object result = method.invoke(target, args); // 在方法调用后执行额外的逻辑 System.out.println("After method invocation"); return result; }}
复制代码


接下来,创建原始对象并生成代理对象:


MyInterface originalObject = new MyOriginalObject(); // 创建原始对象MyInvocationHandler invocationHandler = new MyInvocationHandler(originalObject); // 创建代理处理器MyInterface proxyObject = (MyInterface) Proxy.newProxyInstance(        MyInterface.class.getClassLoader(),        new Class[]{MyInterface.class},        invocationHandler); // 创建代理对象
复制代码


最后,通过代理对象调用方法:


proxyObject.doSomething();
复制代码


在调用 proxyObject.doSomething()时,会先执行代理处理器的 invoke()方法中的前置逻辑,然后调用原始对象的 doSomething()方法,最后执行代理处理器的后置逻辑。


通过基于接口的动态代理,我们可以在不修改原始对象的情况下,为其添加额外的功能或行为,例如日志记录、权限检查、性能监控等。

3.4 基于类动态代理

基于类的动态代理是另一种常见的动态代理方式,它使用第三方库或框架来实现。在 Java 中,常用的类动态代理库包括 CGLIB 和 Byte Buddy。以下是一个基于 CGLIB 的类动态代理的示例:


首先,确保在项目中引入 CGLIB 库的依赖。


然后,定义一个原始类(例如 MyClass):


public class MyClass {    public void doSomething() {        System.out.println("Original method");    }}
复制代码


接下来,创建一个类动态代理处理器类(例如 MyMethodInterceptor),继承自 CGLIB 的 MethodInterceptor 接口:


public class MyMethodInterceptor implements 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;    }}
复制代码


在上述代码中,我们实现了 MethodInterceptor 接口的 intercept()方法,该方法在方法调用前后执行额外的逻辑。通过调用 proxy.invokeSuper(obj, args)来调用原始对象的方法。


接着,创建原始对象并生成代理对象:


Enhancer enhancer = new Enhancer();enhancer.setSuperclass(MyClass.class); // 设置原始类enhancer.setCallback(new MyMethodInterceptor()); // 设置代理处理器MyClass proxyObject = (MyClass) enhancer.create(); // 创建代理对象
复制代码


使用 Enhancer 类来创建代理对象。通过 setSuperclass()方法设置原始类,setCallback()方法设置代理处理器,然后调用 create()方法生成代理对象。


最后,通过代理对象调用方法:


proxyObject.doSomething(); // 调用代理对象的方法
复制代码


在调用 proxyObject.doSomething()时,会先执行代理处理器的 intercept()方法中的前置逻辑,然后调用原始对象的 doSomething()方法,最后执行代理处理器的后置逻辑。


通过基于类的动态代理,我们可以在不修改原始类的情况下,为其添加额外的功能或行为,例如日志记录、权限检查、性能监控等。

3.5 动态代理模版代码

代码如下所示Demo代码


private void testProxy() {    RealSubject realSubject = new RealSubject();    ProxyHandler proxyHandler = new ProxyHandler(realSubject);    Subject subject = (Subject) Proxy.newProxyInstance(RealSubject.class.getClassLoader(),            RealSubject.class.getInterfaces(), proxyHandler);    subject.request();}
interface Subject{ void request();}
class RealSubject implements Subject{ @Override public void request(){ System.out.println("request"); }}
/** * 代理类的调用处理器 */class ProxyHandler implements InvocationHandler { private final Subject subject; public ProxyHandler(Subject subject){ this.subject = subject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //定义预处理的工作,当然你也可以根据 method 的不同进行不同的预处理工作 System.out.println("====before===="); Object result = method.invoke(subject, args); System.out.println("====after===="); return result; }}
复制代码

04.动态代理案例

4.1 动态代理和反射

先来了解下反射


  1. Java 反射机制在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。

  2. 这种 动态的获取信息 以及 动态调用对象的方法 的功能称为 java 的反射机制。

  3. 反射机制很重要的一点就是“运行时”,其使得我们可以在程序运行时加载、探索以及使用编译期间完全未知的 .class 文件。换句话说,Java 程序可以加载一个运行时才得知名称的 .class 文件,然后获悉其完整构造,并生成其对象实体、或对其 fields(变量)设值、或调用其 methods(方法)。


那么为什么要用到反射呢?

4.3 Retrofit 核心思想

Retrofit,想必大家都很熟悉,retrofit 其实核心内容就是用了动态代理。


想想 retrofit 是怎么工作的?在 interface 里面写上需要配置的请求方法,并添加一些注解 然后创建出 interface 的实例,就可以直接调用方法进行网络请求了。看看代码:


public interface ApiService {    @POST(RetrofitHelper.APP_V1 + "/banner")    Observable<BaseEntity<List<Banner>>> getBanners();}
//如何调用,如下所示ApiService service = new Retrofit.Builder().baseUrl("").build().create(ApiService.class);service.getBanners().enqueue(callback);
复制代码


只是写了 ApiService 接口和接口下面的 getBanners 方法,然后就可以进行网络请求。所以 retrofit 是代替我们写了网络请求的具体逻辑,也就是完成了代理的这样一个作用。


具体怎么代理的呢?奥秘主要就在这个***.create(ApiService.class)方法***里面,看看源码:


public <T> T create(final Class<T> service) {    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },        new InvocationHandler() {          private final Platform platform = Platform.get();          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)              throws Throwable {            ServiceMethod<Object, Object> serviceMethod =                (ServiceMethod<Object, Object>) loadServiceMethod(method);            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);            return serviceMethod.callAdapter.adapt(okHttpCall);          }        });}
复制代码


看到这个 newProxyInstance 方法了吧,这就是创建动态代理类的方法。invoke 方法里面就是具体去拆解,接口里面方法的一些参数,然后完成网络请求的整个过程了,也就是代理帮你做的一些事情。

05.动态代理思想和原理

5.1 动态代理实现机制

1,通过实现 InvocationHandler 接口创建自己的调用处理器;


// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用InvocationHandler handler = new InvocationHandlerImpl(..); 
复制代码


2,通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;


// 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); 
复制代码


3,通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;


// 通过反射从生成的类对象获得构造函数对象Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
复制代码


4,通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。


// 通过构造函数对象创建动态代理类实例Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
复制代码


为了简化对象创建过程,Proxy 类中的 newProxyInstance 方法封装了 2~4,只需两步即可完成代理对象的创建。


// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发InvocationHandler handler = new InvocationHandlerImpl(..);  // 通过 Proxy 直接创建动态代理类实例Interface proxy = (Interface)Proxy.newProxyInstance( classLoader,      new Class[] { Interface.class },      handler );
复制代码


需要注意的是:


  1. 生成的代理类为 public final,不能被继承。

  2. 类名:格式是“$ProxyN”,N 是逐一递增的数字,代表 Proxy 被第 N 次动态生成的代理类,要注意,对于同一组接口(接口的排列顺序也相同),不会重复创建动态代理类,而是返回一个先前已经创建并缓存了的代理类对象。提高了效率。

  3. Proxy 类是 $ProxyN 的父类,这个规则适用于所有由 Proxy 创建的动态代理类。(也算是 java 动态代理的一处缺陷,java 不支持多继承,所以无法实现对 class 的动态代理,只能对于 Interface 的代理)而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。

  4. 代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString,


Demo代码

5.2 动态代理思想

动态代理的思想基于以下几个关键概念:


  1. 代理对象:代理对象是一个与原始对象具有相同接口的对象,它可以替代原始对象进行方法调用。代理对象在运行时动态生成,可以在方法调用前后执行额外的逻辑。

  2. 原始对象:原始对象是需要被代理的对象,它定义了业务逻辑的实现。代理对象通过调用原始对象的方法来实现相同的功能。

  3. 代理处理器:代理处理器是一个中间组件,它实现了代理对象的生成和方法调用的处理逻辑。代理处理器在方法调用前后可以执行额外的逻辑,例如记录日志、权限检查、性能监控等。

  4. 动态生成:动态代理是在运行时动态生成代理对象的过程。通过使用反射或第三方库,可以在运行时创建代理对象,并将其与原始对象关联起来。

5.3 动态代理存在的问题

尽管动态代理在许多情况下非常有用,但也存在一些问题需要考虑:


  1. 性能开销:动态代理涉及到反射机制,这可能导致一定的性能开销。相比直接调用原始对象的方法,动态代理需要额外的方法调用和反射操作,可能会导致一些性能损失。

  2. 限制于接口:Java 的动态代理机制只能代理接口,无法直接代理类。这是因为动态代理是基于接口的,它生成的代理对象实现了与原始对象相同的接口。

  3. 无法代理 final 类和方法:动态代理无法代理被 final 修饰的类和方法。这是因为 final 修饰的类和方法无法被继承或重写,而动态代理需要生成子类或子类的方法来实现代理。

  4. 无法直接访问私有方法和字段:动态代理无法直接访问私有方法和字段。这是因为反射机制只能访问公共的方法和字段。如果需要访问私有成员,可以通过反射设置可访问性,但这可能会破坏封装性。

  5. 序列化问题:动态代理对象通常无法序列化。由于代理对象是在运行时动态生成的,无法保证其可序列化。如果需要序列化代理对象,可能需要采用其他方法或框架。

  6. 调试困难:由于动态代理的逻辑是在运行时生成的,调试可能会变得更加困难。在调试代理对象时,可能需要跟踪反射调用和代理处理器的逻辑。

5.4 动态代理的优势

  1. 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在 InvocationHandler 中的 invoke 方法调用的,所以我们只要在 invoke 方法中统一处理,就可以对所有被代理的方法进行相同的操作了。例如,这里的方法计时,所有的被代理对象执行的方法都会被计时,然而我只做了很少的代码量。

  2. 动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然,清晰明了。因为动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体样子,而且动态代理中被代理对象和代理对象是通过 InvocationHandler 来完成的代理过程的,其中具体是怎样操作的,为什么代理对象执行的方法都会通过 InvocationHandler 中的 invoke 方法来执行。

06.动态代理总结

6.1 总结一下学习

01.为何要动态代理


一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色(委托类),该如何使用代理呢?这个问题可以通过 Java 的动态代理类来解决。


02.动态代理的概念


动态代理是指:代理类是在运行期间生成的,也就是说 Java 编译完之后并没有实际的 class 文件,而是在运行时动态生成的类字节码,并加载到 JVM 中。


动态代理定义:给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象。


03.动态代理的实现


代理模式的角色分四种:


  1. 主题接口:Subject,是委托对象和代理对象都共同实现的接口,即代理类的所实现的行为接口。

  2. 目标对象:ReaSubject,是原对象,也就是被代理的对象。

  3. 代理对象:Proxy,是代理对象,用来封装真是主题类的代理类。

  4. 客户端:使用代理类和主题接口完成一些工作。


04.动态代理案例


组成元素:


  1. 抽象类接口

  2. 被代理类(具体实现抽象类接口的类)

  3. 动态代理类,实际调用被代理类的方法和属性


动态代理有两种实现方式:


  1. 基于接口动态代理。一种常见的动态代理方式,它通过实现 Java 的 Proxy 类和 InvocationHandler 接口来实现。

  2. 基于类动态代理。另一种常见的动态代理方式,它使用第三方库或框架来实现。在 Java 中,常用的类动态代理库包括 CGLIB。


05.动态代理思想和原理


动态代理实现机制,主要是通过关键类ProxyInvocationHandler来实现的。


  1. 创建InvocationHandler处理器对象,主要作用是通过实现方法,调用从代理类到委托类的分派转发。

  2. 通过Proxy创建代理对象,传入指定 ClassLoader 对象和一组 interface 来创建动态代理类,里面实现是通过反射去动态创建对象。

6.2 更多内容推荐


23 种设计模式


6.3 更多内容


发布于: 刚刚阅读数: 3
用户头像

杨充

关注

还未添加个人签名 2018-07-30 加入

还未添加个人简介

评论

发布
暂无评论
06.动态代理设计模式_杨充_InfoQ写作社区