JDK动态代理是代理模式的一种,且只能代理接口。spring也有动态代理,称为CGLib,现在主要来看一下JDK动态代理是如何实现的?

一、介绍

JDK动态代理是有JDK提供的工具类Proxy实现的,动态代理类是在运行时生成指定接口的代理类,每个代理实例(实现需要代理的接口)都有一个关联的调用处理程序对象,此对象实现了InvocationHandler,最终的业务逻辑是在InvocationHandler实现类的invoke方法上。

也即是在invoke方法上可以实现原方法中没有的业务逻辑,相当于spring aop的@Before、@After等注解。

二、样例

(1)接口

public interface ProxySource {
void test();
}

(2)实现类

public class ProxySourceImpl implements ProxySource {
@Override
public void test() {
System.out.println("原有业务逻辑");
}
}

(3)实现InvocationHandler接口和invoke方法

static class MyHandler implements InvocationHandler {
//需要代理的类
Object target;
public MyHandler(Object target) {
this.target = target;
}
/**
* @param proxy 动态代理实例
* @param method 需要执行的方法
* @param args 方法中参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("do other things befor");
method.invoke(target, args);
System.out.println("do other things after");
return null;
}
}

(4)利用Proxy实现代理类

//此参数设置是为了保存生成代理类的字节码文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
ProxySource proxySource = (ProxySource) Proxy.newProxyInstance(
ProxySourceImpl.class.getClassLoader(),
ProxySourceImpl.class.getInterfaces(),
new MyHandler(new ProxySourceImpl())
);
//执行方法
proxySource.test();

(5)方法调用结果

可以看到,在原有方法执行前后都执行了其他代码。

三、源码分析(主要看Proxy.newProxyInstance方法,省略非核心代码)

(1)newProxyInstance方法

Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
{
final Class<?>[] intfs = interfaces.clone();
//获取代理接口class
Class<?> cl = getProxyClass0(loader, intfs);
//获取到class之后用反射获取构造方法,然后创建代理类实例
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
return cons.newInstance(new Object[]{h});
} catch (Exception e) {
throw new InternalError(e.toString(), e);
}
}

由上述代码可知,主要是通过getProxyClass0方法获取到代理接口的class

(2)getProxyClass0方法

Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
//如果已经有相应的字节码文件,则之间返回,否则通过代理类工厂创建代理类
return proxyClassCache.get(loader, interfaces);
}

而proxyClassCache又是什么东东呢?

WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

proxyClassCache是一个WeakCache对象,可知这是一个缓存对象,这个类结构是通过ConcurrentHashMap实现的,

ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
= new ConcurrentHashMap<>();

数据结构是(key,sub-key)->value

存的值也就是<ClassLoader,<interfaces,$Proxy.class>>

(3)get方法

public V get(K key, P parameter) {
Object cacheKey = CacheKey.valueOf(key, refQueue);
//根据classloader为key查看缓存中是否已有
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
if (valuesMap == null) {
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
} //获取到weakcache种的sub-key
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
//根据sub-key去当前类加载器下是否有该代理接口的字节码
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null; while (true) {
if (supplier != null) {
//supplier是代理类工厂实例
V value = supplier.get();
if (value != null) {
return value;
}
}
//创建代理类工厂
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
//将上述创建的代理类工厂直接赋值给supplier
if (supplier == null) {
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
supplier = factory;
}
} else {
if (valuesMap.replace(subKey, supplier, factory)) {
supplier = factory;
} else {
supplier = valuesMap.get(subKey);
}
}
}
}

这个supplier.get方法点进去,核心就是ProxyClassFactory的apply方法

(4)ProxyClassFactory的apply方法

Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

      for (Class<?> intf : interfaces) {
Class<?> interfaceClass = null;
try {
//通过类权限定名反射获取class
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
//判断是否可以通过系统加载器加载
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
//校验是否是接口
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
} long num = nextUniqueNumber.getAndIncrement();
//生成代理类的权限定名,例如com.sun.proxy.$Proxy0
String proxyName = proxyPkg + proxyClassNamePrefix + num; //生成字节码文件
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
//调用本地方法生成class
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}

上述代码中核心是生成字节码,即是ProxyGenerator.generateProxyClass方法。

(5)ProxyGenerator.generateProxyClass方法。

public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
//生成字节码
final byte[] var4 = var3.generateClassFile();
//开头的设置
if (saveGeneratedFiles) {
//保存生成代理类的字节码文件
}
return var4;
}

上述代码中saveGeneratedFiles点进去是这样的,也即是开头样例中的设置属性。

private static final boolean saveGeneratedFiles =
((Boolean)AccessController
.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();

但主要还是ProxyGenerator的generateClassFile这个方法。

其默认给代理类生成了hashcode、equals和toString方法,也限制了代理接口和字段都不能超过65535个。

现在来看一下保存的代理类字节码文件是怎么样的(通过idea反编译后)

public final class $Proxy0 extends Proxy implements ProxySource {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0; public $Proxy0(InvocationHandler var1) throws {
super(var1);
} public final boolean equals(Object var1) throws {
//代码省略
} public final void test() throws {
//h是invocationhandler,所以最后是执行invoke方法
super.h.invoke(this, m3, (Object[])null);
} public final String toString() throws {
//代码省略
} public final int hashCode() throws {
//代码省略
} static {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.liusy.lang.ProxySource").getMethod("test");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
}
}
 

可以看到,生成的$Proxy0继承了Proxy,实现了我定义的接口ProxySource,里面有四个方法,m0~m3,通过静态代码块中根据类的全限定名和方法名反射获取,而最后是执行InvocationHandler的invoke方法。

至此,JDK动态代理已经说完,希望对你有所帮助。

=======================================================

我是Liusy,一个喜欢健身的程序员。

欢迎关注微信公众号【Liusy01】,一起交流Java技术及健身,获取更多干货。

JDK动态代理详解的更多相关文章

  1. SpringBoot27 JDK动态代理详解、获取指定的类类型、动态注册Bean、接口调用框架

    1 JDK动态代理详解 静态代理.JDK动态代理.Cglib动态代理的简单实现方式和区别请参见我的另外一篇博文. 1.1 JDK代理的基本步骤 >通过实现InvocationHandler接口来 ...

  2. JDK动态代理详解-依赖接口

    0. 原理分析 a). 自定义实现InvocationHandler类,实现代理类执行时的invoke方法 b). 使用Proxy.newProxyInstance生成接口的代理类(入参还包括Invo ...

  3. JDK、CGlib动态代理详解

    Java动态代理之JDK实现和CGlib实现(简单易懂)      一 JDK和CGLIB动态代理原理 1.JDK动态代理 利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生 ...

  4. 基于SpringBoot实现AOP+jdk/CGlib动态代理详解

    动态代理是一种设计模式.在Spring中,有俩种方式可以实现动态代理--JDK动态代理和CGLIB动态代理. JDK动态代理 首先定义一个人的接口: public interface Person { ...

  5. JDK动态代理、CGLIB动态代理详解

    Spring的AOP其就是通过动态代理的机制实现的,所以理解动态代理尤其重要. 动态代理比静态代理的好处: 1.一个动态代理类可以实现多个业务接口.静态代理的一个代理类只能对一个业务接口的实现类进行包 ...

  6. 【spring基础】AOP概念与动态代理详解

    一.代理模式 代理模式的英文叫做Proxy或Surrogate,中文都可译为”代理“,所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动.在一些情况下,一个客户不想或者不能够直接引用一 ...

  7. JAVA动态代理详解

    1.什么是代理 代理模式是常用的Java 设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等. 2.什么是动态代理 在程 ...

  8. Java 动态代理详解

    package com.at221; //代理设计模式: interface ClothFactory{ void product(); } class NikeFactory implements ...

  9. Java技术整理1---反射机制及动态代理详解

    1.反射是指在程序运行过程中动态获取类的相关信息,包括类是通过哪个加载器进行加载,类的方法和成员变量.构造方法等. 如下示例可以通过三种方法根据类的实例来获取该类的相关信息 public static ...

随机推荐

  1. python numpy数组操作2

    数组的四则运算 在numpy模块中,实现四则运算的计算既可以使用运算符号,也可以使用函数,具体如下例所示: #加法运算 import numpy as npmath = np.array([98,83 ...

  2. latex:在公式之中和公式之间插入说明文字和标点符号

    在公式之中和公式之间插入说明文字和标点符号,主要使用 \intertext{文本} \shortintertext{文本} \text{文本} 这三个命令 代码: \begin{align*}x^{2 ...

  3. Java数据结构——AVL树

    AVL树(平衡二叉树)定义 AVL树本质上是一颗二叉查找树,但是它又具有以下特点:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树,并且拥有自平衡机制.在AV ...

  4. curl报错60的问题

    使用curl发请post请求的时候,会遇到如下错误: curl: (60) SSL certificate problem: self signed certificate More details ...

  5. 分分钟玩转UI自动化测试

    有没有那么一刻,看到自动模拟用户操作界面感觉好神奇. 关于什么叫 UI 自动化测试就不解释了,基本上是你刚才脑海里想到什么就是什么. 在分层自动化测试中包括:UI 测试.集成/接口测试.单元测试.大神 ...

  6. 超简单!基于Python搭建个人“云盘”

    1. 简介 当我们想要从本地向云服务器上传文件时,比较常用的有pscp等工具,但避免不了每次上传都要写若干重复的代码,而笔者最近发现的一个基于Python的工具updog,可以帮助我们在服务器上搭建类 ...

  7. Unity 深度冲突的解决方法

    Dillon|2014-02-12 10:00|5899次浏览|Unity(280)0 3d游戏中当2个片元距离近裁减平面 w  落在同一个区间的时候,他们的深度是相等的. 最终你所看到的结果,就是下 ...

  8. Postman发送POST请求到Spring Boot的正确姿势

    最近用Spring Boot搭建了一些restful api,写起来真的很爽.但是当用Postman测试一些POST请求的接口的时候却遇到一些问题,上网冲浪查了一堆博客资料,发现都讲得不清不楚,于是记 ...

  9. require exports module.exports

    require 用于引入模块(js文件). JSON.或本地文件 自己写的模块必须是相对路径,省略了node就认为该自定义模块(js文件)是核心模块(内置模块或者第三方模块) node  有模块作用域 ...

  10. 剑指 Offer 56 - II. 数组中数字出现的次数 II

    题目描述 在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次.请找出那个只出现一次的数字. 示例1: 输入:nums = [3,4,3,3] 输出:4 示例2: 输入:nums = ...