初步探究jdk动态代理的原理

硅谷探秘者 2476 0 7

初步探究jdk动态代理

1.先看代码实现

package club.jiajiajia.test.staticproxy;

public interface BuyHouse {
    void buyHosue();//买房子
}
package club.jiajiajia.test.staticproxy;

public class BuyHouseImpl implements BuyHouse {

    @Override
    public void buyHosue() {
        System.out.println("买房子");
    }
}
package club.jiajiajia.test.dynamicproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicProxyHandler implements InvocationHandler {
    private Object object;
    public DynamicProxyHandler(Object object) {
        this.object = object;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("买房前准备");
        Object result = method.invoke(object, args);
        System.out.println("买房后装修");
        return result;
    }
}
public static void main(String[] args) {
    try {
	//生成$Proxy0的class文件
	System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");//必须在main函数中才能生成
	//获取动态代理类字节码文件
	Class proxyClazz = Proxy.getProxyClass(BuyHouse.class.getClassLoader(),BuyHouse.class);
	//获得代理类的构造函数
	Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
	//通过构造函数来创建动态代理对象,将自定义的InvocationHandler实例传入
	BuyHouse proxyBuyHouse = (BuyHouse) constructor.newInstance(new DynamicProxyHandler(new BuyHouseImpl()));
	//通过代理对象调用目标方法
	proxyBuyHouse.buyHosue();
	System.out.println();
	BuyHouse buyHouse = new BuyHouseImpl();//未被代理对象
	System.out.println("proxyBuyHouse 对象是否是 Proxy 的实例:"+(proxyBuyHouse instanceof Proxy));
	System.out.println("buyHouse 对象是否是  BuyHouse 的实例:"+(buyHouse instanceof BuyHouse));
	System.out.println("proxyBuyHouse 对象是否是  BuyHouse 的实例:"+(proxyBuyHouse instanceof BuyHouse));
	System.out.println("proxyBuyHouse 对象是否是 BuyHouseImpl 的实例:"+(proxyBuyHouse instanceof BuyHouseImpl));
	System.out.println("代理对象的全类名:"+proxyBuyHouse.getClass().getName());
	System.out.println("代理类的父类:"+proxyBuyHouse.getClass().getSuperclass().getName());
	Class<?>[] c=proxyBuyHouse.getClass().getInterfaces();
	System.out.println();
	for (Class<?> inte : c) {//打印
	    System.out.println("代理类实现接口:"+inte);
	}
    } catch (Exception e) {
		e.printStackTrace();
    }
}

运行打印:

买房前准备
买房子
买房后装修

proxyBuyHouse 对象是否是 Proxy 的实例:true
buyHouse 对象是否是  BuyHouse 的实例:true
proxyBuyHouse 对象是否是  BuyHouse 的实例:true
proxyBuyHouse 对象是否是 BuyHouseImpl 的实例:false
代理对象的全类名:com.sun.proxy.$Proxy0
代理类的父类:java.lang.reflect.Proxy

代理类实现接口:interface club.jiajiajia.test.staticproxy.BuyHouse

2.运行原理

    动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。 

也就是说我们用的代理类的对象已经不是BuyHouseImpl的类模板所生成的对象了,而是jdk在运行期间由JVM根据反射等机制动态生成代理类的字节码文件 类似 $Proxy0.class,代理类的对象就是以这个.class文件为类模板创建的对象,but我们看不到这个.class文件,但是java提供了一个方法可以把这个class文件保存在本地。

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");//必须在main函数中才能生成

QQ截图20181224180339.png

然后用反编译工具反编译这个文件如下:

package com.sun.proxy;

import club.jiajiajia.test.staticproxy.BuyHouse;
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 BuyHouse {
	private static Method m1;
	private static Method m2;
	private static Method m0;
	private static Method m3;
	
	public $Proxy0(InvocationHandler paramInvocationHandler) {
		super(paramInvocationHandler);
	}
	
	public final boolean equals(Object paramObject) {
		try {
			return ((Boolean) this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
		} catch (Error | RuntimeException localError) {
			throw localError;
		} catch (Throwable localThrowable) {
			throw new UndeclaredThrowableException(localThrowable);
		}
	}
	
	public final String toString() {
		try {
			return (String) this.h.invoke(this, m2, null);
		} catch (Error | RuntimeException localError) {
			throw localError;
		} catch (Throwable localThrowable) {
			throw new UndeclaredThrowableException(localThrowable);
		}
	}
	
	public final int hashCode() {
		try {
			return ((Integer) this.h.invoke(this, m0, null)).intValue();
		} catch (Error | RuntimeException localError) {
			throw localError;
		} catch (Throwable localThrowable) {
			throw new UndeclaredThrowableException(localThrowable);
		}
	}
	
	public final void buyHosue() {
		try {
			this.h.invoke(this, m3, null);
			return;
		} catch (Error | RuntimeException localError) {
			throw localError;
		} catch (Throwable localThrowable) {
			throw new UndeclaredThrowableException(localThrowable);
		}
	}
	
	static {
		try {
			m1 = Class.forName("java.lang.Object").getMethod("equals",new Class[] { Class.forName("java.lang.Object") });
			m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
			m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
			m3 = Class.forName("club.jiajiajia.test.staticproxy.BuyHouse").getMethod("buyHosue", new Class[0]);
		} catch (NoSuchMethodException localNoSuchMethodException) {
			throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
		} catch (ClassNotFoundException localClassNotFoundException) {
			throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
		}
	}
}

观察这个$Proxy0.java类会发现有如下几点:

1.这个类继承了Proxy类

2.实现了我们所定义的BuyHouse接口

3.它的构造方法中需要传入InvocationHandler实例

4.它重写了buyHosue方法以及其他方法

5.用反射获取了目标方法的引用(用于反射调用)

那么由这几个特点我绘制了下面这个图:


QQ截图20181218141917.png

代理对象的创建

从上图中可以发现 Proxy 类中有 InvocationHandler 属性,(注意:这个属性与 我们定义的DynamicProxyHandler类有着直接的关系,)

Class proxyClazz = Proxy.getProxyClass(BuyHouse.class.getClassLoader(),BuyHouse.class);
这一行代码是在帮我们创建代理类的类文件
Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
这个是获得该类的构造方法,并由该方法创建代理对象
BuyHouse proxyBuyHouse = (BuyHouse) constructor.newInstance(new DynamicProxyHandler(new BuyHouseImpl()));
这一步才是真正的根据代理类的构造方法创建代理对象,会发现参数我们传入了DynamicProxyHandler的实例

在创建代理类的过程中 $Proxy0 的构造方法有调用了父类的构造方法 并传入DynamicProxyHandler的实例,并且可以看到,

父类的InvocationHandler属性引用了该对象.....其他的不重要.....至此,代理对象创建完成

代理方法的调用

当在调用BuyHouse的buyHosue()方法的时候,实际是调用了代理对象重写了BuyHouse接口的buyHosue()方法即:

	public final void buyHosue() {
		try {
			this.h.invoke(this, m3, null);
			return;
		} catch (Error | RuntimeException localError) {
			throw localError;
		} catch (Throwable localThrowable) {
			throw new UndeclaredThrowableException(localThrowable);
		}
	}

此方法紧接着调用了父类的InvocationHandler属性(即 h对象)的invoke方法(即我们定义的DynamicProxyHandler类的invoke方法,),在其方法内又通过反射调用的我们在创建DynamicProxyHandler对象时传入的目标对象buyHosue()方法

所以我们可以在其调用前后做一些其他的事情。

到此方法调用结束。


这就是我理解的jdk动态代理

对于代理类时如何生成的,这还得继续研究。



评论区
请写下您的评论...
暂无评论...
猜你喜欢
java基础 3073 cglib之前我们说了一下jdkhttp://www.jiajiajia.club/weblog/blog/artical/60本章说一下cglib,做个
ASM,java基础 998   关于cglib概念和以及常用api,请参考:cglib:http://www.jiajiajia.club/blog/artical/yjw520
weblog 4206 :http://www.jiajiajia.club/search?str=asm jdkhttp://www.jiajiajia.club/weblog/blog/artical/60
java基础 2756 java静示例packageclub.jiajiajia.test.staticproxy;publicinterfaceBuyHouse{voidbuyHosue();//买房子
ASM,java基础 897 概念和,这里就不多说了,详细请参考以往博客:cglib:http://www.jiajiajia.club/blog/artical/yjw520/134cglib
linux系统 6216 方案一:一般配置方案二:访问某个域名时资源文件方案一:一般配置  如果以html、htm、gif、jpg、jpeg、bmp、png、ico、txt、js、css结尾资源,均由nginx处
其他 3902 1.编译技术从JDK1.6开始引入了用Java码重写编译器接口,使得我们可以在运行时编译Java源码,然后用类加载器进行加载,让Java语言更具灵活性,能够完成许多高级操作。2.本次要实现
weblog 4060 int不需要。 int默认值是0,而Integer默认值是null。 然而仅仅知道这些,对其两者了解还是远远不够。那么下面就继续索。 语法糖味道-自装箱和拆箱 什么是自装箱和
归档
2018-11  12 2018-12  33 2019-01  28 2019-02  28 2019-03  32 2019-04  27 2019-05  33 2019-06  6 2019-07  12 2019-08  12 2019-09  21 2019-10  8 2019-11  15 2019-12  25 2020-01  9 2020-02  5 2020-03  16 2020-04  4 2020-06  1 2020-07  7 2020-08  13 2020-09  9 2020-10  5 2020-12  3 2021-01  1 2021-02  5 2021-03  7 2021-04  4 2021-05  4 2021-06  1 2021-07  7 2021-08  2 2021-09  8 2021-10  9 2021-11  16 2021-12  14 2022-01  7 2022-05  1 2022-08  3 2022-09  2 2022-10  2 2022-12  5 2023-01  3 2023-02  1 2023-03  4 2023-04  2 2023-06  3 2023-07  4 2023-08  1 2023-10  1 2024-02  1 2024-03  1 2024-04  1
标签
算法基础 linux 前端 c++ 数据结构 框架 数据库 计算机基础 储备知识 java基础 ASM 其他 深入理解java虚拟机 nginx git 消息中间件 搜索 maven redis docker dubbo vue 导入导出 软件使用 idea插件 协议 无聊的知识 jenkins springboot mqtt协议 keepalived minio mysql ensp 网络基础 xxl-job rabbitmq haproxy srs 音视频 webrtc javascript
目录
没有一个冬天不可逾越,没有一个春天不会来临。最慢的步伐不是跬步,而是徘徊,最快的脚步不是冲刺,而是坚持。