代理模式和Java中的动态代理【开发实践】

07-06 1345阅读

文章目录

    • 一、代理模式基础
      • 1.1 代理模式
      • 1.2 静态代理
      • 1.3 动态代理
      • 二、静态代理的实现
      • 三、JDK动态代理
        • 3.1 JDK动态代理概述
        • 3.2 invoke方法介绍
        • 3.3 JDK动态代理的使用
        • 四、CGLIB动态代理
          • 3.1 CGLIB动态代理概述
          • 3.2 CGLIB动态代理的使用
          • 五、对比
            • 5.1 代理实现与使用对比
            • 5.2 使用条件对比
            • 5.3 增强逻辑的位置对比

              一、代理模式基础

              1.1 代理模式

              代理模式是常见的设计模式之一,代理模式就是代理对象具备真实对象的功能,能代替真实对象完成相应操作,并能在操作执行的前后进行增强处理。

              代理模式和Java中的动态代理【开发实践】
              (图片来源网络,侵删)

              代理模式常应用于面向切面编程(AOP)、日志记录、权限控制、性能监控等多种横切关注点的系统级服务。

              代理模式的实现可以分为两类:一类是静态代理,另一类是动态代理。

              1.2 静态代理

              静态代理在程序运行前就已经存在,指由程序员需要手动编写或使用特定工具生成代理类的源代码(即.class 文件),在程序编译阶段就已确定。

              静态代理需要为每个目标类手动编写代理类,可能导致代码重复。适用于代码结构相对固定且代理类数量有限的场景。

              1.3 动态代理

              动态代理是在程序运行期间动态生成的。代理不是直接写成 .class 文件,而是在JVM运行时通过反射、字节码操作(如Java的 java.lang.reflect.Proxy 类或第三方库如CGLIB)等机制动态构建的。

              动态代理提供了更高的灵活性和扩展性,但会牺牲少许性能。

              二、静态代理的实现

              先理解静态代理的实现,更容易理解JDK动态代理的原理,其使用流程如下:

              1. 定义一个接口,里面有需要被代理/增强的方法。
              2. 定义一个需要被代理的类,实现上述接口。
              3. 定义一个静态代理类,也实现上述接口,并且需要定义接口类型的成员变量,用于指向被代理的对象。在静态代理类中,可以对接口方法进行增强,内部可以调用被代理对象的对应方法,并在前后做增强处理。
              4. 使用时,用接口申明变量类型,指向静态代理类的实例对象。
              // 接口
              interface Subject {
              	// 需要被增强的方法
                  void request();
              }
              // 实现类
              class RealSubject implements Subject {
                  @Override
                  public void request() {
                      System.out.println("RealSubject: Handling request.");
                  }
              }
              // 静态代理类
              class StaticProxy implements Subject {
              	// 成员变量用于指向被代理的对象,使用接口类型申明该变量
                  private final Subject realSubject;
              	// 可以用构造器或者set方法来设置被代理的对象
                  public StaticProxy(Subject realSubject) {
                      this.realSubject = realSubject;
                  }
                  @Override
                  public void request() {
                  	// 可选的前置处理
                      preRequest();
                      // 调用被代理的对象
                      realSubject.request();
                      // 可选的后置处理
                      postRequest();
                  }
                  private void preRequest() {
                      System.out.println("StaticProxy: Pre-processing request.");
                  }
                  private void postRequest() {
                      System.out.println("StaticProxy: Post-processing request.");
                  }
              }
              // 使用静态代理对象
              public class StaticProxyTest {
                  public static void main(String[] args) {
                  	// 使用接口申明变量类型,指向静态代理类的实例对象
                      Subject subject = new StaticProxy(new RealSubject());
                      subject.request();
                  }
              }
              

              三、JDK动态代理

              3.1 JDK动态代理概述

              JDK动态代理是一种利用Java反射机制在运行时动态创建代理对象的技术,它是Java原生支持的,主要依赖于java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。

              JDK 动态代理基于接口,只有实现了接口的类,才能通过 JDK 动态代理来增强接口方法。

              3.2 invoke方法介绍

              • Object proxy:代理对象的引用。尽管在invoke方法内部可能不需要直接使用它,但它代表了代理实例本身,有时候可用于获取代理类的信息或其他特殊处理。
              • Method method:表示被调用的方法的一个Method对象。这个对象包含了方法的所有元数据,如方法名、返回类型、参数类型等。你可以利用这个对象来判断被调用的是哪个具体方法,从而做出不同的处理逻辑。
              • Object[] args:一个对象数组,包含了调用方法时传递的实际参数。这些参数与原始方法调用时提供的参数类型和顺序完全一致。你可以利用这些参数来传递给被代理方法,或者进行预处理/后处理
              • 返回值:invoke方法的返回值会作为实际调用方法的返回值。

                3.3 JDK动态代理的使用

                JDK动态代理的使用流程如下:

                1. 定义一个接口,里面有需要被代理/增强的方法。
                2. 定义一个需要被代理的类,实现上述接口。
                3. 定义一个动态代理处理器,需要实现InvocationHandler接口,并且需要定义接口类型的成员变量,用于指向被代理的对象。实现invoke()方法,可以对接口方法进行增强。
                4. 使用时,用接口申明变量类型,指向Proxy.newProxyInstance()返回的代理对象。
                // 动态代理处理器
                class DynamicProxyHandler implements InvocationHandler {
                    private final Subject realSubject;
                    public DynamicProxyHandler(Subject realSubject) {
                        this.realSubject = realSubject;
                    }
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        preRequest();
                        // 调用被代理对象的方法
                        // 调用接口方法时,这里就会调用被代理对象的相应方法
                        Object result = method.invoke(realSubject, args);
                        postRequest();
                        return result;
                    }
                    private void preRequest() {
                        System.out.println("DynamicProxyHandler: Pre-processing request.");
                    }
                    private void postRequest() {
                        System.out.println("DynamicProxyHandler: Post-processing request.");
                    }
                }
                // 测试动态代理
                public class DynamicProxyTest {
                    public static void main(String[] args) {
                        Subject realSubject = new RealSubject();
                        // 返回的是Object对象,需要转换为目标接口对象
                        Subject proxySubject = (Subject) Proxy.newProxyInstance(
                                RealSubject.class.getClassLoader(),
                                new Class[]{Subject.class},
                                new DynamicProxyHandler(realSubject)
                        );
                        proxySubject.request();
                    }
                }
                

                四、CGLIB动态代理

                3.1 CGLIB动态代理概述

                CGLIB(Code Generation Library)是一个强大的、高性能的代码生成库,它允许在运行时动态地生成和修改Java字节码。

                CGLIB动态代理基于子类继承,能够对任何类(无论是否实现了接口)实现代理,这使得它的应用范围更广。

                3.2 CGLIB动态代理的使用

                CGLIB动态代理的使用流程如下:

                1. 定义目标类(无需实现接口)。
                2. 定义方法拦截器,需要实现MethodInterceptor接口。实现intercept()方法来对被代理对象的方法进行增强。
                3. 使用时,需要创建方法拦截器对象和Enhancer对象,通过Enhancer对象来返回代理对象。该操作可以封装在方法拦截器中。
                // 目标类,无需实现接口
                class RealSubject {
                    public void request() {
                        System.out.println("RealSubject: Handling request.");
                    }
                }
                // CGLIB代理实现
                class CglibProxy implements MethodInterceptor {
                    @Override
                    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                        preRequest();
                        Object result = proxy.invokeSuper(obj, args); // 调用父类方法,即真实对象的方法
                        postRequest();
                        return result;
                    }
                    private void preRequest() {
                        System.out.println("CglibProxy: Pre-processing request.");
                    }
                    private void postRequest() {
                        System.out.println("CglibProxy: Post-processing request.");
                    }
                    // 创建代理对象
                    public Object getProxy(Class clazz) {
                        Enhancer enhancer = new Enhancer();
                        enhancer.setSuperclass(clazz); // 设置父类
                        enhancer.setCallback(this); // 设置回调方法
                        return enhancer.create(); // 创建并返回代理对象
                    }
                }
                // 测试CGLIB动态代理
                public class CglibProxyTest {
                    public static void main(String[] args) {
                        CglibProxy cglibProxy = new CglibProxy();
                        // 返回的是Object对象,需要转换为被代理类的对象
                        RealSubject proxySubject = (RealSubject) cglibProxy.getProxy(RealSubject.class);
                        proxySubject.request();
                    }
                }
                

                五、对比

                5.1 代理实现与使用对比

                实现代理逻辑

                都需要调用目标方法,然后在目标方法前后新增增强逻辑。区别是,静态代理在实现接口方法时,调用被代理对象的目标方法并实现增强。动态代理需要实现接口(InvocationHandler / MethodInterceptor),必须实现接口方法(invoke / intercept),在接口方法里调用被代理对象的目标方法并实现增强。

                获取代理对象

                静态代理时,被代理类的对象即代理对象。

                JDK动态代理中,使用Proxy.newProxyInstance()来获取代理对象。

                CGLIB动态代理中,使用enhancer.create()来获取代理对象。

                5.2 使用条件对比

                静态代理和CGLIB动态代理都依赖于接口,CGLIB动态代理不依赖于接口。

                5.3 增强逻辑的位置对比

                静态代理中,增强逻辑分散在被代理类的各个方法中,需要在方法中实现该方法的增强逻辑。如果这些方法都需要同种增强,将产生大量的重复代码。

                在动态代理中,增强逻辑集中在一个方法里。如果所有方法都需要同种增强,增强逻辑只需要写一处,非常方便。如果方法的增强逻辑不同,则需要通过反射获取方法名,然后使用响应的增强逻辑。

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]