Java中的动态代理

这篇具有很好参考价值的文章主要介绍了Java中的动态代理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、Java中的两种动态代理方式

Java中常用的有两种动态代理方式,分别为:JDK动态代理和Cglib动态代理。

JDK动态代理是通过实现接口的方式完成动态代理。

Cglib动态代理是通过继承目标类或实现接口的方式完成动态代理。

二、JDK动态代理

JDK动态代理中最核心的就是Proxy类和InvocationHandler接口。

Proxy类

通过调用这个类中的静态方法newProxyInstance(),可以返回代理对象。

newProxyInstance()方法需要三个参数,分别是:ClassLoader类加载、目标对象实现的所有接口、InvocationHandler实现类。

InvocationHandler接口

这个接口中有一个invoke方法,代理对象通过调用这个方法对目标方法进行增强。

三、案例

需求

需求:创建计算器类,进行加减乘除运算。同时需要在进行加减乘除之前打印参数信息,在加减乘除之后打印计算结果。

创建类实现功能

创建Calculator计算器接口,提供加减乘除方法,创建实现类,并完成打印参数与计算结果的功能。

接口:

package com.demo.dynamic_proxy.jdk;

/**
 * 计算器接口 有加减乘除功能
 */
public interface Calculator {
    int add(int a, int b);  // 加法
    int sub(int a, int b);  // 减法
    int mul(int a, int b);  // 乘法
    int div(int a, int b);  // 除法
}

实现类:

public class CalculatorImpl implements Calculator {

    @Override
    public int add(int a, int b) {
        System.out.println("执行add方法,参数信息:" + a + "," + b);
        int result = a + b;
        System.out.println("add方法执行完毕,计算结果:" + result);
        return result;
    }

    @Override
    public int sub(int a, int b) {
        System.out.println("执sub方法,参数信息:" + a + "," + b);
        int result = a + b;
        System.out.println("sub方法执行完毕,计算结果:" + result);
        return result;
    }

    @Override
    public int mul(int a, int b) {
        System.out.println("执行mul方法,参数信息:" + a + "," + b);
        int result = a + b;
        System.out.println("mul方法执行完毕,计算结果:" + result);
        return result;
    }

    @Override
    public int div(int a, int b) {
        System.out.println("执行div方法,参数信息:" + a + "," + b);
        int result = a + b;
        System.out.println("div方法执行完毕,计算结果:" + result);
        return result;
    }
}

分析存在的问题

如果将打印参数信息和打印计算结果的代码都写到实现类中,会出现以下问题:

1)代码比较分散,过于冗余 ,可以将打印参数信息和结果的代码提取到工具类中。

2)代码过于混乱,非核心业务代码(输出语句)与核心业务代码(加减乘除操作)混合到了一起,不好维护。

可以通过动态代理的方式,对功能进行增强,不需要在业务代码(加减乘除的运算)中添加非核心业务代码(打印参数和结果的代码)

改进代码

将代码提取到工具类中

package com.demo.dynamic_proxy.jdk.util;

import java.util.Arrays;

public class Logging {
    /**
     * 方法执行前打印参数
     * @param className     类名
     * @param methodName    方法名
     * @param args          执行方法时的参数
     */
    public static void beforeMethod(String className, String methodName, Object[] args){
        System.out.println("执行" + className + "类中的" + methodName + "方法,参数:" + Arrays.toString(args));
    }

    /**
     * 方法执行后打印执行结果
     * @param className     类名
     * @param methodName    方法名
     * @param result        方法的返回值
     */
    public static void afterMethod(String className, String methodName, Object result){
        System.out.println(className + "类中的" + methodName + "方法执行完毕,结果:" + result);
    }
}

使用动态代理的方式对功能进行增强

增强的功能:方法执行之前打印参数、方法执行之后打印结果。

实现步骤,分为四步:

1)创建类【用来生成代理对象】

2)在类中提供目标对象属性 ,需要被增强的对象。

3)在类中提供有参构造器为目标对象属性赋值 。

4)提供获取代理对象的方法。

package com.demo.dynamic_proxy.jdk;

import com.demo.dynamic_proxy.jdk.util.Logging;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyProxy {

    // 目标对象,需要增强的对象
    private Object target;

    // 有参构造器为目标对象进行赋值
    public MyProxy(Object target) {
        this.target = target;
    }

    // 获取代理对象的方法
    public Object getProxyObject(){
        // 代理对象
        Object proxyObject = null;
        // 获取类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();
        // 获取目标对象实现的所有接口
        Class<?>[] interfaces = target.getClass().getInterfaces();
        // 调用Proxy.newProxyInstance()方法,创建代理对象
        proxyObject = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
            /**
             * @param proxy     代理对象
             * @param method    要执行的方法
             * @param args      执行方法时需要的参数
             * @return  调用目标对象方法后返回的执行结果
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 目标对象类名
                String targetClassName = target.getClass().getName();
                // 要执行的方法名
                String targetMethodName = method.getName();
                // 在方法调用之前,打印方法参数
                Logging.beforeMethod(targetClassName, targetMethodName, args);
                // 调用目标对象的方法
                Object result = method.invoke(target, args);
                // 调用方法之后,输出执行结果
                Logging.afterMethod(targetClassName, targetMethodName, result);
                // 将执行结果返回
                return result;
            }
        });
        // 将代理对象返回
        return proxyObject;
    }
}

测试代码

@Test
public void test(){
    // 创建目标对象,也就是需要被增强的对象
    CalculatorImpl calculator = new CalculatorImpl();
    // 创建MyProxy并获取代理对象
    MyProxy myProxy = new MyProxy(calculator);
    Calculator proxyObject = (Calculator) myProxy.getProxyObject();
    // 调用方法
    proxyObject.add(1, 2);
}

执行结果:

执行com.demo.dynamic_proxy.jdk.impl.CalculatorImpl类中的add方法,参数:[1, 2]
正在执行add方法......
com.demo.dynamic_proxy.jdk.impl.CalculatorImpl类中的add方法执行完毕,结果:3

我们通过动态代理的方式完成了参数和执行结果的打印功能,以后需要调整打印信息时,只需要修改一处即可。

四、底层原理分析

JDK动态代理是如何生成代理对象的呢?底层真的没有创建类吗?

底层其实是有创建类的,Java中必须有类才能够使用这个类的实例,JDK动态代理运行时,底层创建代理类实现目标对象的接口,并生成代理类的字节码,通过类加载器将字节码进行加载,最后再生成代理类对象实例。

查看Proxy类的newProxyInstance()方法:

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

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        // 通过这个方法来生成代理类
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        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<Void>() {
                    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);
        }
    }

其中,通过getProxyClass0方法来获取代理类

Class<?> cl = getProxyClass0(loader, intfs);

getProxyClass0方法中有一段核心的代码:

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);

这段代码就是生成代理类的字节码信息的。

我们试着调用这个方法,并获取到class字节码:

@Test
public void test01(){
    // 要生成的类的名称
    String className = "myClass";
    // 指定class类名以及代理类要实现的接口
    byte[] bytes = ProxyGenerator.generateProxyClass(className, new Class[]{Calculator.class});
    FileOutputStream fos = null;
    try {
        // 将生成的字节信息输出到 myClass.class
        fos = new FileOutputStream(new File("D:\\" + className + ".class"));
        fos.write(bytes);
    }catch (Exception e){
        System.out.println(e.getMessage());
    }finally {
        if(fos != null){
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

使用反编译工具打开class文件,这里使用的是IDEA:

java动态代理的两种方式,java,Powered by 金山文档

字节码中的add方法:

java动态代理的两种方式,java,Powered by 金山文档

完整代码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import com.demo.dynamic_proxy.jdk.Calculator;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class myClass extends Proxy implements Calculator {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m3;
    private static Method m6;
    private static Method m5;
    private static Method m0;

    public myClass(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int mul(int var1, int var2) throws  {
        try {
            return (Integer)super.h.invoke(this, m4, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final int add(int var1, int var2) throws  {
        try {
            return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final int sub(int var1, int var2) throws  {
        try {
            return (Integer)super.h.invoke(this, m6, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final int div(int var1, int var2) throws  {
        try {
            return (Integer)super.h.invoke(this, m5, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.demo.dynamic_proxy.jdk.Calculator").getMethod("mul", Integer.TYPE, Integer.TYPE);
            m3 = Class.forName("com.demo.dynamic_proxy.jdk.Calculator").getMethod("add", Integer.TYPE, Integer.TYPE);
            m6 = Class.forName("com.demo.dynamic_proxy.jdk.Calculator").getMethod("sub", Integer.TYPE, Integer.TYPE);
            m5 = Class.forName("com.demo.dynamic_proxy.jdk.Calculator").getMethod("div", Integer.TYPE, Integer.TYPE);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

五、总结

  1. JDK动态代理是通过实现接口的方式完成对目标对象功能的增强。

  1. JDK动态代理底层创建代理类,实现目标对象所实现的接口,并生成代理类的字节码信息,通过类加载器进行加载,最后创建代理类对象,通过代理对象调用InvocationHandler接口实现类的invoke方法完成功能的增强。

  1. 通过调用Proxy类的newProxyInstance方法创建代理对象,代理对象调用InvocationHandler接口中的invoke实现对目标对象的增强功能。文章来源地址https://www.toymoban.com/news/detail-578187.html

到了这里,关于Java中的动态代理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 线程方法接收参数和返回参数,Java的两种线程实现方式对比

    总所周知,Java实现多线程有两种方式,分别是继承Thread类和实现Runable接口,那么它们的区别是什么? 继承 Thread 类: 通过继承 Thread 类,你可以创建一个直接表示线程的类。你可以覆盖 Thread 类中的 run 方法来定义线程的逻辑。当调用 start 方法启动线程时,会执行该类中的

    2024年02月11日
    浏览(45)
  • 【Redis,Java】Redis的两种序列化方式—nosql数据库

    redis和mysql的区别: redis是属于nosql的数据库,而mysql是属于sql数据库,redis是属于nosql数据库。mysql是存储在磁盘中的,redis是存储在内存中的,所以redis的读取书读快。这里所说的redis代表nosql,而mysql代表sql。 redis的数据库是以键值对为基础存储在内存中的,而mysql为代表的关

    2024年02月21日
    浏览(57)
  • ES的两种认证登录方式: JAVA REST Client/HTTP Client

    通过RestHighLevelClient发送的es请求验证方式: 通过http直接发送的es请求认证方式:

    2024年02月16日
    浏览(38)
  • uni-app中代理的两种配置方式

    方式一: 在项目的 manifest.json 文件中点击 源码视图 在最底部的vue版本下编写代理代码 方式二: 在项目中创建 vue.config.js 文件然后进行配置 在页面中发起请求  完整的url:http://c.m.163.com/recommend/getChanListNews?channel=T1457068979049size=10 总结:代理的配置是为了解决发起请求时的跨域

    2024年01月23日
    浏览(44)
  • 使用Vue脚手架配置代理服务器的两种方式

    本文主要介绍使用Vue脚手架配置代理服务器的两种方式 注意:Vue脚手架给我们提供了两种配置代理服务器的方式,各有千秋,使用的时候只能二选一,不能同时使用 除了cros和jsonp,还有一种代理方式,这种用的相对来说也很多, 一般代理服务器 这个概念很好理解,相当于生

    2024年02月02日
    浏览(69)
  • axios和vite在本地开发环境配置代理的两种方式,五分钟学会

    如果你使用vue或者react开发,就得使用axios吧,然后为了解决跨域问题,就得使用vite配置吧,那怎么协调配置它们两个才能正常工作呢? 正常的流程:配置axios的baseURL,然后配置vite的proxy 配置axios的baseURL: 然后再配置vite的proxy:在vite.config.js中配置 如果你想将路径重写也是

    2024年02月04日
    浏览(38)
  • MySQL中的两种特殊插入方式

    代码案例 PointMapper.java PointMapper.xml 代码案例 PointMapper.java PointMapper.xml on duplicate key update 和 replace into 是两种处理重复键冲突的方法,但它们具有一些区别 功能不同 on duplicate key update 在插入数据时,如果遇到重复键冲突,会更新已存在的行的值 replace into在插入数据时,如果遇

    2024年02月12日
    浏览(60)
  • VS2019编译生成动态链接库dll的两种方式

     dll项目的默认结构如下:  四个文件的内容因为是默认生成的,不是特别重要, 接下来就是重要的修改部分: 方法一: 修改“pch.h”和“dllmain.cpp”文件,可以参考以下博主链接,但博主的引用部分有些繁琐,文末会介绍我的引用方法,和正常引用外部库步骤是一样的。这

    2023年04月09日
    浏览(49)
  • java 将word转为pdf文件的两种方式【spire.doc.free】【documents4j】

    如资产证明等场景下,一般要求同时生成word与pdf两种格式的证明文件,且两者格式需保持一致,可以各自单独生成,但那样可能需要维护两个模板文件,所以也可以仅定义一份word的模板文件,使用模板生成word文件,再将word转换为pdf,这样不仅少维护一个模板,也可以保证

    2024年02月12日
    浏览(61)
  • Java中动态代理的实现方式

    动态代理(Dynamic Proxy)是一种在运行时创建代理对象的机制,它允许我们在不事先知道具体类型的情况下,通过代理对象来调用目标对象的方法。 动态代理通常使用在面向对象编程中的AOP(面向切面编程)中,用于在目标对象的方法执行前后,添加一些额外的逻辑(如日志

    2024年02月15日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包