返回顶部
首页 > 资讯 > 后端开发 > JAVA >Java动态代理四种实现方式详解
  • 14
分享到

Java动态代理四种实现方式详解

Java动态代理Java代理模式 2022-06-07 23:06:46 14人浏览 佚名
摘要

代理模式也是一种非常常见的设计模式。了解spring框架的都知道,Spring aop 使用的就是动态代理模式。今天就来系统的重温一遍代理模式。在现实生活中代理是随处可见的,当事人因某些隐私不方便出面,或者当事人不具备某些相关的专业技能,而

代理模式也是一种非常常见的设计模式。了解spring框架的都知道,Spring aop 使用的就是动态代理模式。今天就来系统的重温一遍代理模式。

在现实生活中代理是随处可见的,当事人因某些隐私不方便出面,或者当事人不具备某些相关的专业技能,而需要一个职业人员来完成一些专业的操作, 也可能由于当事人没有时间处理事务,而聘用代理人出面。而在软件设计中,使用代理模式的地方也很多,由于安全原因,屏蔽客户端直接访问真实对象, 或者为了提升系统性能,使用代理模式实现延迟加载,还有就是AOP,对委托类的功能进行增强等。

一、代理模式的结构

代理模式的主要参与者有4个,如下图所示:


角色作用
Subject主题接口,定义了代理类和委托类的公共对外方法,也是代理类代理委托类的方法
RealSubject委托类,真实主题,真正实现业务逻辑的类
Proxy代理类,代理和封装委托类
Client客户端,使用代理类和主题接口完成业务逻辑


loading="lazy" alt="" />角色作用Subject主题接口,定义了代理类和委托类的公共对外方法,也是代理类代理委托类的方法RealSubject委托类,真实主题,真正实现业务逻辑的类Proxy代理类,代理和封装委托类Client客户端,使用代理类和主题接口完成业务逻辑

二、代理模式的实现

代理模式一般分为静态代理和动态代理两种:

  • 静态代理,顾名思义,就是提前创建好代理类文件并在程序运行前已经编译成字节码。

  • 动态代理,是指在运行时动态生成代理类,即代理类的字节码将在运行时生成并载入到ClassLoader中。

了解了两种代理模式大概区别后,接下来就以一个短信发送功能增强的示例来详细阐述两种代理的实现方式。

1、静态代理实现

第一步,定义主题接口,该接口只有一个send方法:

public interface ISender {
    public boolean send();
}

第二步,定义主题真正实现类:

public class SmsSender implements ISender {
    public boolean send() {
        System.out.println("sending msg");
        return true;
    }
}

第三步,创建代理类,封装实现类:

public class ProxySender implements ISender {
    private ISender sender;
    public ProxySender(ISender sender){
        this.sender = sender;
    }
    public boolean send() {
        System.out.println("处理前");
        boolean result = sender.send();
        System.out.println("处理后");
        return result;
    }
}

第四步,客户端调用:

@Test
public void testStaticProxy(){
    ISender sender = new ProxySender(new SmsSender());
    boolean result = sender.send();
    System.out.println("输出结果:" + result);
}

以上就实现了一个简单的静态代理,很明显,静态代理需要为每个真实主题定义一个形式上完全一样的封装类,

如果真实主题方法有所修改,那代理类也需要跟着修改,不利于系统的维护。

2、动态代理实现

与静态代理相比,动态代理有更多优势,动态代理不仅不需要定义代理类,甚至可以在运行时指定代理类的执行逻辑,从而大大提升系统的灵活性。

目前动态代理类的生成方法有很多,有jdk自带的动态代理、CGLIB、Javassist和ASM库等。

  • JDK动态代理:内置在JDK中,不需要引入第三方jar,使用简单,但功能比较弱。

  • CGLIB/Javassist:这两个都是高级的字节码生成库,总体性能比JDK动态代理好,且功能强大。

  • ASM:低级字节码生成工具,近乎使用bytecode编码,对开发人员要求最高。当然性能也是最好(相比前几种也不是很大的提升,这里不做具体介绍)。

以下实例依然以SmsSenderISender作为被代理对象和接口进行试验。

1) JDK动态代理

JDK的动态代理需要实现一个处理方法调用的Handler,用于实现代理方法的内部逻辑,实现InvocationHandler接口。

public class JdkProxyHandler implements InvocationHandler {
    private Object target;
    public JdkProxyHandler(Object target){
        this.target = target;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("处理前");
        Object result = method.invoke(target,args);
        System.out.println("处理后");
        return result;
    }
}

客户端调用:

@Test
public void testJdkProxy(){
    ISender sender = (ISender) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
            new Class[]{ISender.class},
            new JdkProxyHandler(new SmsSender()));
    boolean result = sender.send();
    System.out.println("代理对象:" + sender.getClass().getName());
    System.out.println("输出结果:" + result);
}

输出结果:

处理前

sending msg

处理后

代理对象:com.sun.proxy.$Proxy4

输出结果:true

这样实现一个简单的AOP就完成了,我们看到代理类的类型是com.sun.proxy.$Proxy4。那JDK是如何创建代理类?

首先从Proxy.newProxyInstance入手,来研究JDK是如何生成代理类:

public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

该方法有3个参数:

  • loader:用哪个类加载器去加载代理对象,生成目标对象的代理需要确保其类加载器相同,所以需要将目标对象的类加载器作为参数传递。

  • interfaces:代理类需实现的接口列表,JDK动态代理技术需要代理类和目标对象都继承自同一接口,所以需要将目标对象的接口作为参数传递。

  • h:调用处理器,调用实现了InvocationHandler类的一个回调方法,对目标对象的增强逻辑在这个实现类中。

具体代码如下:

public static Object newProxyInstance(ClassLoader loader,
                                          Class[] interfaces,
                                          InvocationHandler h) throws IllegalArgumentException {
    //1.检查
    Objects.requireNonNull(h);
    final Class[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }
    
    //获取代理类类型
    Class cl = getProxyClass0(loader, intfs);
    
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }
        //通过反射创建代理对象
        final Constructor cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

总结:具体代码细节就不在这里深究,但可以明显的看出,JDK的动态代理底层是通过Java反射机制实现的,并且需要目标对象继承自一个接口才能生成它的代理类。

2) CGLIB(Code Generation Library)动态代理

使用CGLIB动态代理前需要引入依赖:

    cglib    cglib    3.3.0

和JDK代理不同,CGLib动态代理技术不需要目标对象实现自一个接口,只需要实现一个处理代理逻辑的切入类,并实现MethodInterceptor接口。

定义真实主题实现类:

public class BdSender {
    public boolean send() {
        System.out.println("sending msg");
        return true;
    }
}

代理类逻辑处理类:

public class CglibProxyInterceptor implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();
    
    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("处理前");
        Object result = methodProxy.invokeSuper(object,args);
        System.out.println("处理后");
        return result;
    }
}

客户端调用:

@Test
public void testCglibProxy(){
    BdSender sender = (BdSender) new CglibProxyInterceptor().getProxy(BdSender.class);
    boolean result = sender.send();
    System.out.println("代理对象:" + sender.getClass().getName());
    System.out.println("输出结果:" + result);
}

输出结果:

处理前

sending msg

处理后

代理对象:org.yd.proxy.BdSender$$EnhancerByCGLIB$$d65f9e34

输出结果:true


总结CgLib的特点:

  • 使用CGLib实现动态代理,完全不受代理类必须实现接口的限制

  • CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高

  • CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类

3)Javassist动态代理

Javassist是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。

相对于bcel, asm等这些工具,开发者不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

使用avassist动态代理前需要引入依赖:

    org.javassist    javassist    3.27.0-GA

使用Javassist生成动态代理可以有以下两种方式:

  • 代理工厂创建:需要实现MethodHandler用于代理逻辑处理,实现与CGLib非常类似

  • 动态代码创建:可通过Java代码生成字节码,这种方式创建的动态代理非常灵活,甚至可以在运行时生成业务逻辑

代理工厂创建 — 代理逻辑处理类

public class JavassistProxyHandler implements MethodHandler {
    private ProxyFactory proxyFactory = new ProxyFactory();
    
    public Object getProxy(Class clazz) throws Exception {
        proxyFactory.setSuperclass(clazz);
        Class factoryClass = proxyFactory.createClass();
        Object proxy = factoryClass.newInstance();
        ((ProxyObject)proxy).setHandler(this);
        return proxy;
    }
    public Object invoke(Object object, Method method, Method method1, Object[] args) throws Throwable {
        System.out.println("处理前");
        Object result = method1.invoke(object,args);
        System.out.println("处理后");
        return result;
    }
}

客户端调用:

@Test
public void testJavassistProxy() throws Exception {
    BdSender sender = (BdSender) new JavassistProxyHandler().getProxy(BdSender.class);
    boolean result = sender.send();
    System.out.println("代理对象:" + sender.getClass().getName());
    System.out.println("输出结果:" + result);
}

输出结果

处理前

sending msg

处理后

代理对象:org.yd.proxy.BdSender_$$_jvstbce_0

输出结果:true

动态代码创建 — 代理逻辑处理类:

public static Object getProxy(Class clazz) throws Exception {
    ClassPool mPool = ClassPool.getDefault();
    CtClass c0 = mPool.get(clazz.getName());
    //定义代理类名称
    CtClass mCtc = mPool.makeClass(clazz.getName() + "$$BytecodeProxy");
    //添加父类继承
    mCtc.setSuperclass(c0);
    //添加类的字段信息
    CtField field = new CtField(c0, "real", mCtc);
    field.setModifiers(AccessFlag.PRIVATE);
    mCtc.addField(field);
    //添加构造函数
    CtConstructor constructor = new CtConstructor(new CtClass[]{c0},mCtc);
    constructor.setBody("{$0.real = $1;}"); // $0代表this, $1代表构造函数的第1个参数
    mCtc.addConstructor(constructor);
    //添加方法
    CtMethod ctMethod = mCtc.getSuperclass().getDeclaredMethod("send");
    CtMethod newMethod = new CtMethod(ctMethod.getReturnType(), ctMethod.getName(),ctMethod.getParameterTypes(), mCtc);
    newMethod.setBody("{" +
            "System.out.println(\"处理前\");" +
            "boolean result = $0.real.send();" +
            "System.out.println(\"处理后\");" +
            "return result;}");
    mCtc.addMethod(newMethod);
    //生成动态类
    return mCtc.toClass().getConstructor(clazz).newInstance(clazz.newInstance());
}

客户端调用:

@Test
public void testJavassisBytecodetProxy() throws Exception {
    BdSender sender = (BdSender) JavassistDynamicCodeProxy.getProxy(BdSender.class);
    boolean result = sender.send();
    System.out.println("代理对象:" + sender.getClass().getName());
    System.out.println("输出结果:" + result);
}

输出结果:

处理前

sending msg

处理后

代理对象:org.yd.proxy.BdSender$$BytecodeProxy

输出结果:true

Javassist被用于struts2和hibernate中,都用来做动态字节码修改使用。一般开发中不会用到,但在封装框架时比较有用。

以上介绍了静态代理和动态代理创建的几种方法与优缺点介绍,希望可以帮到大家。

到此这篇关于Java四种动态代理实现方式的文章就介绍到这了,更多相关Java动态代理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Java动态代理四种实现方式详解

本文链接: https://lsjlt.com/news/30811.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Java动态代理四种实现方式详解
    代理模式也是一种非常常见的设计模式。了解Spring框架的都知道,Spring AOP 使用的就是动态代理模式。今天就来系统的重温一遍代理模式。在现实生活中代理是随处可见的,当事人因某些隐私不方便出面,或者当事人不具备某些相关的专业技能,而...
    99+
    2022-06-07
    Java动态代理 Java代理模式
  • Java 动态代理的多种实现方式
    目录一、动态代理简介二、动态代理的多种实现 1. 基于JDK的实现 2. 基于cglib的实现 三、为什么要有基于cglib的实现 四、两种方式的适用场景JDK动态代理 优点 缺点 ...
    99+
    2024-04-02
  • 深入理解java动态代理的两种实现方式(JDK/Cglib)
    什么是代理模式?代理模式:在调用处不直接调用目标类进行操作,而是调用代理类,然后通过代理类来调用目标类进行操作。在代理类调用目标类的前后可以添加一些预处理和后处理操作来完成一些不属于目标类的功能。为什么要使用代理模式?通过代理模式可以实现对...
    99+
    2023-05-31
    cglib jdk java
  • 代理模式:JAVA静态代理和动态代理的实例和实现详解
    目录前言静态代理实现简述创建human接口创建接口实现类创建针对接口实现增强操作的代理代理实现效果动态代理实现简述要点:向上转型创建YoungMan接口创建两个接口实现类创建动态代理...
    99+
    2024-04-02
  • Java实现AOP代理的三种方式详解
    目录1、JDK实现 2、CGLIB实现 3、boot注解实现【注意只对bean有效】业务场景:首先你有了一个非常好的前辈无时无刻的在“教育&rdquo...
    99+
    2024-04-02
  • Java实现JDK动态代理的原理详解
    目录概念案例静态代理JDK动态代理模式原理分析真相大白概念 代理:为控制A对象,而创建出新B对象,由B对象代替执行A对象所有操作,称之为代理。一个代理体系建立涉及到3个参与角色:真实...
    99+
    2024-04-02
  • Java详解实现多线程的四种方式总结
    目录前言一、四种方式实现多线程1.继承Thread类创建线程2.实现Runnable接口创建线程3.实现Callable接口4.实现有返回结果的线程二、多线程相关知识1.Runnab...
    99+
    2024-04-02
  • JAVA代理,静态,动态详解
    目录代理静态代理动态代理JDK动态代理CGLib动态代理总结代理 为其他对象提供一种代理以控制这个对象的访问,在某些情况下一个对象不能直接访问那个对象时,代理就起到了客户端和被代理对...
    99+
    2024-04-02
  • JDK动态代理,代理接口没有实现类,实现动态代理方式
    目录JDK动态代理,代理接口没有实现类,实现动态代理被代理的接口:代理对象:那么接下来测试一下:jdk动态代理为什么要接口先通过一个简单例子实现功能:编写测试方法:里面的getPro...
    99+
    2024-04-02
  • Python实现单例模式的四种方式详解
    简介:单例模式可以保证一个类仅有一个实例,并提供一个访问它的全局访问点。适用性于当类只能有一个实例而且客户可以从一个众所周知的访问点访问它,例如访问数据库、MQ等。 实现方式: 1、...
    99+
    2024-04-02
  • Java动态代理模式怎么实现
    这篇文章主要介绍“Java动态代理模式怎么实现”,在日常操作中,相信很多人在Java动态代理模式怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java动态代理模式怎么实现”的疑惑有所帮助!接下来,请跟...
    99+
    2023-06-17
  • JAVA的动态代理详解
    文章目录 前言一、动态是什么?二、使用步骤1.导入相应的包2.定义接口3.定义接口实现类4.实现InvocationHandler接口5.实现代理 三、整体实例四、输出结果总结 前言...
    99+
    2023-08-31
    java 开发语言
  • java jdk动态代理详解
    jdk动态代理要对一个类进行代理,被代理的类必须实现至少一个接口,并且只有接口中的方法才能被代理。 jdk实现动态代理一般分为三步: 1. 编写接口和实现类。 2. 写一个处理器,该...
    99+
    2022-11-15
    java jdk 动态代理
  • java动态代理的实现方法
    这篇文章主要介绍“java动态代理的实现方法”,在日常操作中,相信很多人在java动态代理的实现方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”java动态代理的实现方法”的疑惑有所帮助!接下来,请跟着小编...
    99+
    2023-06-20
  • Java的动态代理和静态代理详解
    目录0、代理模式1、静态代理2、 加深理解3、动态代理动态代理的例子总结0、代理模式 为什么要学习代理模式?这是SpringAOP的底层【SpringAOP和SpringMVC】 代...
    99+
    2024-04-02
  • java动态代理实现代码
    目录1、代理模式2、动态代理3、原理研究4、应用5、总结1、代理模式 代理模式是常用的设计模式之一,也是开发中常见的设计模式。 简单的描述一下,代理模式就是将实现类隔离开,比如你想给...
    99+
    2024-04-02
  • Java之MyBatis的Dao方式以及Dao动态代理详解
    目录MyBatis传统Dao方式以及Dao动态代理MyBatis传统Dao方式MyBatis之Dao动态代理总结MyBatis简介以及入门参见以下文章 MyBatis简介及入门 1、...
    99+
    2022-12-21
    Java MyBatis MyBatis的Dao方式 MyBatis Dao动态代理
  • Java静态代理与动态代理案例详解
    代理模式 代理模式(Proxy):为其他对象提供一个代理以控制对这个对象的访问。 主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象...
    99+
    2024-04-02
  • Shell四种运行方式(启动方式)的实现
    Shell 是一个应用程序,它的一端连接着 linux 内核,另一端连接着用户。Shell 是用户和 Linux 系统沟通的桥梁,我们都是通过 Shell 来管理 Linux 系统。 我们可以直接使用 Shell,也可以...
    99+
    2022-06-04
    Shell 运行方式 Shell 启动方式
  • 详解java 中的动态代理
    这期内容当中小编将会给大家带来有关详解java 中的动态代理,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Java动态代理相关先来看静态代理模式代码:package test; public inter...
    99+
    2023-05-31
    java 动态代理 ava
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作