IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 揭开动态代理的神秘面纱 -> 正文阅读

[Java知识库]揭开动态代理的神秘面纱


本篇文章针对的是对动态代理想要进行深入研究的小伙伴而写,即我们深入源码进行研究


JDK动态代理

小例子

先上一个动态代理的小例子:

public interface IRequest {
    public void doGet() throws InterruptedException ;
}
public class Request implements IRequest{
    @Override
    public void doGet() throws InterruptedException {
        System.out.println("处理请求中...");
        Thread.sleep(new Random().nextInt(10));
    }
}
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Request request=new Request();
        IRequest proxyInstance = (IRequest)Proxy.newProxyInstance(request.getClass().getClassLoader(),
                request.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        long start = System.currentTimeMillis();
                        Object invoke = method.invoke(request, args);
                        long end = System.currentTimeMillis();
                        System.out.println("请求耗时为 :" + String.valueOf(end - start) + "毫秒");
                        return invoke;
                    }
                });
      proxyInstance.doGet();
    }
}

在这里插入图片描述


JDK动态代理源码探究

首先,我们利用ProxyGenerator来手动生成上面Request的代理对象,放入到当前项目的target目录下面,这样我们就可以利用编译器自动反编译class文件的功能,而不需要自己手动去反编译了

public class Main {
    public static void main(String[] args) throws InterruptedException {
      //生成Request类的代理对象
        byte[] proxyClass = ProxyGenerator.generateProxyClass("$Proxy0", Request.class.getInterfaces());
        //将这个二进制class流写入文件中
        String path=System.getProperty("user.dir")+"/target/classes/com/RequestProxy.class";
        try(FileOutputStream out=new FileOutputStream(path))
        {
            out.write(proxyClass);
            out.flush();
            System.out.println("写入完毕");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
生成的代理对象源码如下:

import com.staticProxy.IRequest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements IRequest {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    //父类有一个InvocationHandler  h成员变量
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
             //调用 InvocationHandler  的invoke方法,也就是需要我们自己去实现的这个invoke方法
            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 {
              //调用 InvocationHandler  的invoke方法,也就是需要我们自己去实现的这个invoke方法
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void doGet() throws InterruptedException {
        try {
              //调用 InvocationHandler  的invoke方法,也就是需要我们自己去实现的这个invoke方法
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | InterruptedException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
              //调用 InvocationHandler  的invoke方法,也就是需要我们自己去实现的这个invoke方法
            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");
            //代理对象只会重写被代理对象实现的接口中的方法,而不会实现被代理对象自身额外添加的方法
            m3 = Class.forName("com.staticProxy.IRequest").getMethod("doGet");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}


InvocationHandler接口

我相信看了上面jdk默认生成的代理对象源码,大家肯定对InvocationHandler和Proxy特别感兴趣,下面我们逐个击破,首先是InvocationHandler接口的分析

public interface InvocationHandler {
    //代理对象,当前执行的方法,当前方法需要的参数
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

其实这就是个函数式接口,只有一个需要用户实现的invoke方法。

        IRequest proxyInstance = (IRequest)Proxy.newProxyInstance(request.getClass().getClassLoader(),
                request.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        long start = System.currentTimeMillis();
                        Object invoke = method.invoke(request, args);
                        long end = System.currentTimeMillis();
                        System.out.println("请求耗时为 :" + String.valueOf(end - start) + "毫秒");
                        return invoke;
                    }
                });

在我们通过Proxy对象的newProxyInstance方法创建代理对象的时候,我们会在该方法第三个参数传入InvocationHandler 接口的一个实现类,然后生成的代理对象继承Proxy类,并且通过Proxy类的InvocationHandler类型的成员变量保存了我们传入的InvocationHandler接口的实现类,然后在代理对象执行每个被代理对象方法时,都是通过InvocationHandler的invoke方法执行的


Proxy类

我们来分析一下神秘的newProxyInstance方法:

  @CallerSensitive
    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---通过传入的类加载器和被代理对象实现的接口数组
         //这里还会让代理对象继承Proxy类
        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;
                    }
                });
            }
            //生成代理对象的实例,传入InvocationHandler h作为构造器参数
            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);
        }
    }

Proxy其他注意点:

public class Proxy implements java.io.Serializable {

    private static final long serialVersionUID = -2222568056686623797L;

    //规定了生成的代理对象构造方法参数类型
    private static final Class<?>[] constructorParams =
        { InvocationHandler.class };

    //会缓存已经生成的代理类
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
    
    //Proxy类中通过h变量保存从构造方法中传入的InvocationHandler对象实例
    protected InvocationHandler h;

 
    private Proxy() {
    }

     //需要传入InvocationHandler的父类构造函数 
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
    .....

在这里插入图片描述
结论: 红色画出的那一行代码会造成死循环,大家思考为什么?

上面说过代理对象的方法调用最终会调用InvocationHandler的invoke方法,那如果我们在InvocationHandler的invoke方法中继续调用代理对象的方法,不就进入一个死循环了吗?

我们这里的本意是通过InvocationHandler的invoke方法对被代理对象的方法进行增强,因此这里method对象关联的实例对象应该是被代理的对象,这里大家不要搞混了。


Cglib代理

CGLIB是一个强大的高性能的代码生成包。

CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承);

利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

Cglib的原理这里不会剖析的特别深入,感兴趣的小伙伴可以自行去查看他的源码,顺便还可以学习一下ASM框架的使用


导入相关依赖

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>

小例子

public class Request{
    public void doGet() throws InterruptedException {
        System.out.println("处理请求中...");
        Thread.sleep(new Random().nextInt(10));
    }
}
//实现MethodInterceptor接口
public class ProxyFactory implements MethodInterceptor
{
    //需要代理的目标对象
    private  Object target;

    public ProxyFactory(Object target)
    {
        this.target=target;
    }

    //获取代理对象的方法
    public Object getProxyInstance()
    {
        // 通过CGLIB动态代理获取代理对象的过程
        Enhancer enhancer=new Enhancer();
        // 设置enhancer对象的父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
        enhancer.setSuperclass(target.getClass());
        //设置enhancer的回调对象
        enhancer.setCallback(this);
        // 创建代理对象
        return enhancer.create();
    }

    /**
     * @param o cglib生成的代理对象
     * @param method 被代理对象的方法
     * @param objects     传入方法的参数
     * @param methodProxy 代理的方法
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects,
                            MethodProxy methodProxy) throws Throwable {
        long start = System.currentTimeMillis();
        //这里必须传入被代理的对象,否则会死循环
        //因为代理对象方法调用会触发拦截器
        Object ret= method.invoke(target, objects);
        long end = System.currentTimeMillis();
        System.out.println("请求耗时为 :" + String.valueOf(end - start) + "毫秒");
        return ret;
    }
}
public class Main {
    public static void main(String[] args) throws InterruptedException {
        ProxyFactory proxyFactory=new ProxyFactory(new Request());
        Request proxyInstance = (Request) proxyFactory.getProxyInstance();
        proxyInstance.doGet();
    }
}

在这里插入图片描述


  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-21 20:35:37  更:2022-03-21 20:37:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:45:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码