当前位置: 首页 > article >正文

Java 静态代理与动态代理全面讲解

一、代理模式的作用

Java中代理模式是一种常见的设计模式,代理模式可以在不改变原有代码的情况下增强类的功能。代理模式包括静态代理和动态代理两种形式,AOP的底层机制就是动态代理。

代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。

下面将对两种代理模式进行全面讲解。

二、静态代理

静态代理是在编译期间已经确定了代理类的代码,代理类和被代理类都实现了同一个接口或者是继承自同一个父类。静态代理的优点是编写简单,易于理解和维护。但是,如果需要代理多个类,代理类的数量会增加,并且在代理类和被代理类的方法增加时需要手动维护代理类的代码。

下面是一个简单的静态代理示例:

public interface Subject {
    void request();
}

public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject:request()");
    }
}

public class ProxySubject implements Subject {
    private RealSubject realSubject;

    public ProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        System.out.println("ProxySubject:before request()");
        realSubject.request();
        System.out.println("ProxySubject:after request()");
    }
}

public class Client {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        ProxySubject proxySubject = new ProxySubject(realSubject);
        proxySubject.request();
    }
}

 在上述示例中,RealSubject 是被代理类,ProxySubject 是代理类,代理类实现了 Subject 接口,并且持有被代理类的实例 RealSubject。在代理类的 request() 方法中,调用了被代理类的 request() 方法,并在前后添加了自己的处理逻辑。

三、动态代理

动态代理是在运行时动态生成代理类的代码,不需要事先知道代理类的代码。Java中的动态代理主要有两种形式,一种是基于接口的动态代理,另一种是基于类的动态代理。

要想实现动态代理,需要解决的问题?

问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象

问题二:当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法

基于接口的动态代理

基于接口的动态代理是指代理类和被代理类实现了同一个接口,代理类在运行时动态生成,不需要手动编写代理类的代码。

在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。

Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。

public static Object newProxyInstance(ClassLoader loader,
                                       Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
{
      ......
}

这个方法一共有 3 个参数:

  1. loader :类加载器,用于加载代理对象。
  2. interfaces : 被代理类实现的一些接口;
  3. h : 实现了 InvocationHandler 接口的对象;

 要实现动态代理的话,还必须需要实现InvocationHandler 来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。

public interface InvocationHandler {

    /**
     * 当你使用代理对象调用方法的时候实际会调用到这个方法
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

invoke() 方法有下面三个参数:

  1. proxy :动态生成的代理类
  2. method : 与代理类对象调用的方法相对应
  3. args : 当前 method 方法的参数

也就是说:你通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。 你可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。

下面是一个基于接口的动态代理示例:

public interface Subject {
    void request();
}
// 被代理类
public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject:request()");
    }
}
// 解决问题二
public class DynamicProxySubject implements InvocationHandler {
    private Object realSubject;

    public DynamicProxySubject(Object realSubject) {
        this.realSubject = realSubject;
    }
    //当我们通过代理类的对象,调用方法request时,就会自动的调用如下的方法: invoke()
    //将被代理类要执行的方法的功能就言明在invoke()中
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //method: 即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
        //realSubject: 被代理类的对象
        System.out.println("DynamicProxySubject:before " + method.getName() + "()");
        Object result = method.invoke(realSubject, args);
        System.out.println("DynamicProxySubject:after " + method.getName() + "()");
        //上述方法的返回值就作为当前类中的invoke()的返回值。
        return result;
    }
}

public class Client {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        DynamicProxySubject dynamicProxySubject = new DynamicProxySubject(realSubject);
        //调用此方法,返回一个代理类的对象。解决问题一
        Subject proxySubject = (Subject)Proxy.newProxyInstance(realSubject.getClass().getClassLoader(),         
        realSubject.getClass().getInterfaces(), dynamicProxySubject);
        proxySubject.request();
    }
}

在上述示例中,`RealSubject` 是被代理类,`DynamicProxySubject` 是代理类,代理类实现了 `InvocationHandler` 接口,并在代理类的 `invoke()` 方法中调用了被代理类的相应方法,并在前后添加了自己的处理逻辑。在客户端代码中,通过调用 `Proxy.newProxyInstance()` 方法动态生成代理类的实例。

基于类的动态代理

基于类的动态代理是指代理类和被代理类都是类,代理类在运行时动态生成,不需要手动编写代理类的代码。Java中的基于类的动态代理主要是通过字节码生成框架来实现的,比较常用的字节码生成框架有CGLIB和ASM。 下面是一个基于类的动态代理示例(使用CGLIB实现):

public class RealSubject {
    public void request() {
        System.out.println("RealSubject:request()");
    }
}

public class DynamicProxySubject implements MethodInterceptor {
    private Object realSubject;

    public DynamicProxySubject(Object realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("DynamicProxySubject:before " + method.getName() + "()");
        Object result = proxy.invoke(realSubject, args);
        System.out.println("DynamicProxySubject:after " + method.getName() + "()");
        return result;
    }
}

public class Client {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealSubject.class);
        enhancer.setCallback(new DynamicProxySubject(new RealSubject()));
        RealSubject proxySubject = (RealSubject) enhancer.create();
        proxySubject.request();
    }
}

在上述示例中,RealSubject 是被代理类,DynamicProxySubject 是代理类,代理类实现了 MethodInterceptor 接口,并在代理类的 intercept() 方法中调用了被代理类的相应方法,并在前后添加了自己的处理逻辑。在客户端代码中,通过调用 CGLIB 提供的 Enhancer 类来生成代理类的实例。

四、总结

静态代理和动态代理都是代理模式的具体实现,它们都可以增强类的功能,但是实现方式不同。静态代理在编译期间已经确定了代理类的代码,适合代理少量的类;动态代理在运行时动态生成代理类的代码,适合代理大量对象或者不确定代理哪个类的情况。

Java中的静态代理需要手动编写代理类的代码,不够灵活,但是效率较高;动态代理不需要手动编写代理类的代码,比较灵活,但是效率较低。

Java中的静态代理和动态代理都是基于接口的代理,因此被代理类必须实现接口。

动态代理在Java中是通过反射机制实现的,反射机制会带来一定的性能开销。另外,由于动态代理是在运行时动态生成的代理类,因此不易调试。

在实际应用中,可以根据具体的情况选择静态代理或动态代理,比如需要代理的类数量、代理类的结构、代理类的复杂度、代理类的生命周期等因素都可以影响选择。


http://www.kler.cn/a/16125.html

相关文章:

  • vite + vue3 + ts解决别名引用@/api/user报错找不到相应的模块
  • 大模型在蓝鲸运维体系应用——蓝鲸运维开发智能助手
  • Vue 组件通信及进阶语法
  • Linux 下 mysql 9.1 安装设置初始密码 【附脚本】
  • JVM详解:类的加载过程
  • MaxKB
  • 14.基于双层优化的电动汽车优化调度研究(文章复现)
  • Word2vec原理+实战学习笔记(二)
  • 【热门框架】Mybatis-Plus入门介绍看这一篇文章就足够了
  • JVM内存模型的演变
  • ChatGPT技术原理 第三章:深度学习基础
  • 代码评审平台Gerrit安装配置方法介绍
  • ChatGPT资料汇总学习
  • 3.6 cache存储器
  • JavaWeb之过滤器Filter(通俗易懂版)
  • 【算法】Dinner Plate Stacks 餐盘栈
  • Codeforces Round 867 (Div 3) 总结
  • JavaScript 中 try...catch 的 10 个使用技巧
  • windows10系统如何实现telnet内网穿透
  • 求n个斐波拉契数列的和
  • mysql性能分析
  • Spring Boot 3.x 系列【27】应用篇之集成Lombok简化开发
  • chatgpt 镜像版
  • 【五一创作】[论文笔记]图片人群计数CSRNet,Switch-CNN
  • Linux基础指令
  • 手把手教你爬取网站信息