大厂高级工程师面试必问系列:Java动态代理机制和实现原理详解

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

// 标记: 用于标记一个动态代理类正在被创建中

private static Object pendingGenerationMarker = new Object();

// 同步表: 记录已经被创建的动态代理类类型,主要通过方法isProxyClass进行相关判断

private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap());

// 关联的调用处理器引用

protected InvocationHandler h;

newProxyInstance
  • Proxy 静态方法 newProxyInstance:

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

/*

  • 检查关联调用处理器是否为空,如果是空则抛出异常

*/

if (h == null) {

throw new NullPointerException();

}

/*

  • 获得与指定类型装载器和一组接口相关的代理类类型对象

*/

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

/*

  • 通过反射获取构造函数对象并生成代理类实例

*/

try {

final Constructor<?> cons = cl.getConstructor(constructorParams);

final InvocationHandler ih = h;

SecurityManager sm = System.getSecurityManager();

if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {

/*

  • 使用doPrivilege创建动态代理类实例

  • 因为代理类实现可能需要特殊权限的非公共接口

*/

return AccessController.doPrivileged(new PrivilegedAction() {

public Object run() {

return newInstance(cons, ih);

}

});

} else {

return newInstance(cons, ih);

}

} catch (NoSuchMethodException e) {

throw new InternalError(e.toString());

}

}

private static Object newInstance(Constructor<?> cons, InvocationHandler h) {

try {

return cons.newInstance(new Object[] {h});

} catch (IllegalAccessException e) {

throw new InternalError(e.toString());

} catch (InstantationException e) {

throw new InternalException(e.toString());

} catch (InvocationTargetException e) {

Throwable t = e.getCause();

if (t instanceof RuntimeException) {

throw (RuntimeException) t;

} else {

throw new InternalException(e.toString());

}

}

}

  • 动态代理的真正的关键是在 getProxyClass0() 方法

getProxyClass0方法分析


  • 通过getProxyClass0方法中生成具体的class文件的过程:

  • 定义 path

  • class 文件写到指定的硬盘中

  • 反编译生成的 class 文件

getProxyClass0()方法分为四个步骤:

  1. 对这组接口进行一定程度的安全检查:

1.1 接口类对象是否对类装载器可见

1.2 接口类对象与类装载器所识别的接口类对象完全相同

1.3 确保是interface类型而不是class类型.

for (int i = 0; i < interfaces.length; i++ ) {

/*

  • 验证类加载器是否将此接口的名称解析为相同的类对象

*/

String interfaceName = interface[i].getName();

Class interfaceClass = null;

try {

/*

  • forName(String name, boolean initialize, ClassLoader loader)

  • Returns the Class object associated with the class or interface with the given string name,

  • using the given class loader

*/

interfaceClass = Class.forName(interfaceName, false, loader);

} catch (ClassNotFoundException e) {

}

if (interfaceClass != interface[i]) {

throw new IllegalArgumentException(interface[i] + “is not visible from class loader.”);

}

/*

  • 验证类加载器得到的类对象是否是interface类型

*/

if (! interfaceClass.isInterface()) {

throw new IllegalArgumentException(interfaceClass.getName() + “is not an interface.”);

}

/*

  • 验证类加载器得到的类对象接口不是一个重复的接口

*/

if (interfaceSet.contains(interfaceClass)) {

throw new IllegalArgumentException(“repeated interface:” + interface.getName());

}

interfaceSet.add(interfaceClass);

interfaceName[i] = interfaceName;

}

  1. loaderToCache 映射表中获取以类装载器对象为关键字所对应的缓存表,如果不存在,就会创建一个新的缓存表并更新到 loaderToCahe 中:

2.1 loaderToCache 存放键值对 : 接口名字列表:动态生成的代理类的类对象的引用

2.2 当代理类正在被创建时,会临时进行保存 : 接口名字列表:pendingGenerationMarker

2.3 标记 pendingGenerationMarker 的作用是通知后续的同类请求(接口数组相同且组内接口排列顺序也相同)代理类正在被创建,请保持等待直至创建完成

/*

  • 寻找类加载器的缓存表,如果没有就为类加载器创建代理类缓存

*/

Map cache;

synchronized (loaderToCache) {

cache = (Map) loaderToCache.get(loader);

if (cache == null) {

cache = new HashMap();

loaderToCache = put(loader, cache);

}

}

do {

/*

  • 以接口名字作为关键字获得对应的cache值

*/

Object value = cache.get(key);

if (value instanceof Reference) {

proxyClass = (Class)((Reference)value).get();

}

if (proxyClass != null) {

// 如果已经创建,直接返回

return proxyClass;

} else if (value == pendingGenerationMarker) {

// 代理类正在创建,保持等待

try {

cache.wait()

} catch (InterruptException e) {

}

// 等待被唤醒,继续循环并通过二次检查以确保创建完成,否则重新等待

continue;

} else {

// 标记代理类正在被创建

cache.put(key, pendingGenerationMarker);

// 跳出循环已进入创建过程

break;

}

} while(true)

  1. 动态创建代理类的class对象

/*

  • 选择一个名字代理类来生成

*/

long num;

synchronized (nextUniqueNumberLock) {

num = nextUniqueNumber ++;

}

String proxyName = proxyPkg + proxyClassNamePrefix + num;

/*

  • 验证类加载器中没有使用这个名字定义的类

*/

// 动态生成代理类的字节码数组

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

try {

// 动态地定义新生成的代理类

proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);

} catch (ClassFormatError e) {

/*

  • 这里的类格式错误指的是生代理类代码中的错误

  • 还有一些应用到代理类生成的参数的错误,比如一些虚拟机限制的超量

*/

throw new IllegalArgumentException(e.toString());

}

// 将生成的代理类对象记录到proxyClasses中

proxyClasses.put(proxyClass, null);

首先根据接口 public 与否的规则生成代理类的名称 - $ProxyN 格式,然后动态生成代理类. 所有的代码生成工作由 ProxyGenerator 完成,该类在 rt.jar 中,需要进行反编译

public static byte[] generateProxyClass(final String name, Class[] interfaces) {

ProxyGenerator gen = new ProxyGenerator(name, interfaces);

// 动态生成代理类的字节码

final byte[] classFile = gen.generateClassFile();

// 如果saveGeneratedFiles的值为true,则会把所生成的代理类的字节码保存到硬盘上

if (saveGeneratedFiles) {

java.security.AccessController.doPrivileged(

new java.security.PrivilegedAction() {

public Void run() {

try{

FileOutputStream file = new FileOutputStream(doToSlash(name) + “.class”);

file.write(classFile);

file.close();

return null;

} catch (IOException e) {

throw new InternalError(“I/O exception saving generated file :” + e);

}

}

}

);

}

// 返回代理类的字节码

return classFile;

}

  1. 代码生成过程进入结尾部分,根据结果更新缓存表. 如果代理类成功生成则将代理类的类对象引用更新进缓存表,否则清除缓存表中对应的关键值,最后唤醒所有可能的正在等待的线程

finally {

synchronized (cache) {

if (proxyClass != null) {

cache.put(key, new WeakReference(proxyClass));

} else {

cache.remove(key);

}

cache.notifyAll();

}

}

return proxyClass;

InvocationHandler解析


  • Proxy 角色在执行代理业务的时候,就是在调用真正业务之前或者之后完成一些额外的功能

img

  • 代理类就是在调用真实角色的方法之前或者之后做一些额外的业务

  • 为了构造具有通用性和简单性的代理类,可以将所有的触发真实角色动作交给一个触发管理器,让这个管理器统一管理触发,这个触发管理器就是 InvocationHandler

  • 动态代理工作的基本工作模式:

  • 将方法功能的实现交给 InvocationHandler 角色

  • 外接对 Proxy 角色中每一个的调用 ,Proxy 角色都会交给 InvocationHandler 来处理

  • InvocationHandle r则调用具体对象角色的方法

img

  • 在这种模式中,代理Proxy和RealSubject应该实现相同的的

public方法,有两种方式:

  • 一个比较直观的方式: 就是定义一个功能接口,然后让 ProxyRealSubject 来实现这个接口 ( JDK 中的动态代理机制 - Java动态代理机制 )

  • 比较隐晦的方式: 通过继承实现 Proxy 继承 RealSubject. 因为Proxy继承自RealSubject, 这样Proxy则拥有了RealSubject的功能 ,Proxy 还可以通过重写 RealSubject 中的方法来实现多态( cglib )

JDK动态代理机制

  • JDK动态代理机制通过接口为RealSubject创建一个动态代理对象:

  • 获取 RealSubject 上的所有接口列表

  • 确定要生成的代理类类名

  • 根据需要实现的接口信息,在代码中动态创建该 Proxy 类的字节码

  • 将对应的字节码转换成对应的 class 对象

  • 创建 InvocationHandler, 用来处理 Proxy 所有方法调用

  • Proxyclass 对象,以创建的 handler 为参数,实例化一个 proxy

  • JDK动态代理实例:

  • 定义两个接口Vehicle和Rechargeable

  • Vehicle接口表示交通工具类,有drive()方法

  • Rechargeable接口表示可充电,有recharge()方法

  • 定义一个实现两个接口的类ElectricCar,类图如下:

img

  • 创建ElectricCar的动态代理类:

/**

  • 交通工具接口

*/

public interface Vehicle {

public void drive();

}

/**

  • 充电接口

*/

public interface Rechargable {

public void recharge();

}

/**

  • 电动车类

  • 实现Rechargable, Vehicle接口

*/

public class ElectricCar implements Rechargable, Vehicle {

@Override

public void drive() {

System.out.println(“ElectricCar can drive.”);

}

@Override

public void recharge() {

System.out.println(“ElectricCar can recharge.”);

}

}

/**

  • 动态代理类的触发器

*/

public class InvocationHandlerImpl implements InvocationHandler {

private ElectricCar car;

public InvocationHandlerImpl(Electric car) {

this.car = car;

}

@Override

public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {

System.out.println(“正在调用方法:” + paramMethod.getName() + “…”);

paramMethod.invoke(car, null);

System.out.println(“方法” + paramMethod.getName() + “调用结束.”);

return null;

}

}

public class ProxyTest {

public static void main(String[] args) {

ElectricCar car = new ElectricCar();

// 获取对应的ClassLoader

ClassLoader classLoader = car.getClass().getClassLoader();

// 获取ElectricCar所实现的所有接口

Class[] interfaces = car.getClass().getInterfaces();

// 设置一个来自代理传过来的方法调用请求处理器,处理所有的代理对象上的方法调用

InvocationHandler handler = new InvocationHandlerImpl(car);

/*

  • 创建代理对象在这个过程中:

  •   a. JDK根据传入的参数信息动态地在内存中创建和 .class 等同的字节码
    
  •   b. 根据相应的字节码转换成对应的class
    
  •   c. 然后调用newInstance()创建实例
    

*/

Object o = Proxy.newProxyInstance(classLoader, interfaces, handler);

Vehicle vehicle = (Vehicle) o;

vehicle.drive();

Rechargable rechargable = (Rechargable) o;

rechargable.recharge();

}

}

  • 生成动态代理类的字节码并且保存到硬盘中:

  • JDK提供了 sun.misc.ProxyGenerator.generateProxyClass(String proxyName, calss[] interfaces) 底层方法来产生动态代理类的字节码

  • 定义一个工具类,用来将生成的动态代理类保存到硬盘中:

public class proxyUtils {

/*

  • 根据类信息,动态生成二进制字节码保存到硬盘中

  • 默认是clazz目录下

  • @params clazz 需要生成动态代理类的类

  • @proxyName 动态生成代理类的名称

*/

public static void generateClassFile(Class clazz, String proxyName) {

// 根据提供的代理类名称和类信息,生成字节码

byte[] classFile = ProxyGenerator.generateProxyClass(ProxyName, clazz.getInterfaces());

String paths = clazz.getResource(“.”).getPath();

System.out.println(paths);

FileOutputStream out = null;

try {

// 保留到硬盘中

out = new FileOutputStream(paths + proxyName + “.class”);

out.write(classFile);

out.flush();

} catch (Exception e) {

e.printStackTrace();

} finally {

try {

out.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

  • 修改代理类名称为 “ElectricCarProxy”, 并保存到硬盘,使用以下语句:

ProxyUtils.generateClassFile(car.getClass(), “ElectricCarProxy”);

这样将在 ElectricCar.class 同级目录下产生 ElectricCarProxy.class 文件

  • 使用反编译工具 jd-gui.exe 打开,将会看到以下信息:

/**

  • 生成的动态代理类的组织模式是继承Proxy类,然后实现需要实现代理的类上的所有接口

  • 在实现过程中,是通过将所有的方法都交给InvocationHandler来处理

*/

public final class ElectricCarProxy extends Proxy implements Rechargable,Vehicle {

private static Method m1;

private static Method m3;

private static Method m4;

private static Method m0;

private static Method m2;

public ElectricCarProxy(InvocationHandler paramInvocationHandler) throws {

super(paramInvocationHandler);

}

public final boolean equals(Object paramObject) throws {

try {

/*

  • 方法功能的实现交给InvocationHandler处理

*/

return ((Boolean) this.h.invoke(this, m1, new Object[] {paramObject})).booleanValue();

} catch (Error | RuntimeException localError) {

throw localError;

} catch (Throwable localThrowable) {

throw new Undeclared ThrowableException(localThrowable);

}

}

public final void recharge() throws {

try {

/*

  • 方法功能的实现交给InvocationHandler处理

*/

this.h.invoke(this, m3, null);

return;

} catch (Error | RuntimeException localError) {

throw localError;

} catch (Throwable localThrowable) {

throw new Undeclared ThrowableException(localThrowable);

}

}

public final drive() throws {

try {

/*

  • 方法实现交给InvocationHandler处理

*/

this.h.invoke(this, m4, null);

return;

} catch (Error | RuntimeException localError) {

throw localError;

} catch (Throwable localThrowable) {

throw new Undeclared ThrowableException(localThrowable);

}

}

public final int hasCode() throws {

try {

/*

  • 方法功能交给InvocationHandler处理

*/

return ((Integer) this.h.invoke(this, m0, null)).intValue();

} catch (Error | RuntimeException localError) {

throw localError;

} catch (Throwable localThrowable) {

throw new Undeclared ThrowableException(localThrowable);

}

}

public final String toString() throws {

try {

/*

  • 方法功能实现交给InvocationHandler处理

*/

return (String)this.h.invoke(this, m2, null);

} catch (Error | RuntimeException localError) {

throw localError;

} catch (Throwable localThrowable) {

throw new Undeclared ThrowableException(localThrowable);

}

}

static {

try {

/*

  • 为每一个需要方法对象

  • 当调用相应的方法时,分别将方法对象作为参数传递给InvocationHandler处理

*/

m1 = Class.forName(“java.lang.Object”).getMethod(“equals”, new Class[] { Class.forName(“java.lang.Object”) });

m3 = Class.forName(“com.oxford.proxy.Rechargable”).getMethod(“recharge”, new Class[0]);

m4 = Class.forName(“com.oxford.proxy.Vehicle”).getMethod(“drive”, new Class[0]);

m0 = Class.forName(“java.lang.Object”).getMethod(“hasCode”, new Class[0]);

m2 = Class.forName(“java.lang.Object”).getMethod(“toString”, new Class[0]);

return;

} catch (NoSuchMethodException localNoSuchMethodException) {

throw new NoSuchMethodError(localNoSuchMethodException.getMessage());

} catch (ClassNotFoundException localClassNotFoundException) {

throw new NoClassDefFoundError(localClassNotFoundException.getMessge());

}

}

}

  • 生成的动态代理类的特点:

  • 继承自 java.lang.reflect.Proxy, 实现了 Rechargable,Vehicle 这两个 ElectricCar 接口

  • 类中的所有方法都是 final

  • 所有的方法功能的实现都统一调用了 InvocationHandlerinvoke() 方法

img

CGLIB动态代理机制

  • CGLIB 通过类继承生成动态代理类

  • JDK动态代理类的特点:

  • 某个类必须有实现的接口,而生成的代理类只能代理某个类接口定以的方法. 这样会导致子类实现继承两个接口的方法外,另外实现的方法,在产生的动态代理类中不会有这个方法

  • 如果某个类没有实现接口,那么这个类就不能使用JDK动态代理了

  • CGLIB: Code Generation Library, CGLIB 是一个强大的,高性能,高质量的 Code 生成类库,可以在运行时期扩展 Java 类与实现 Java 接口

  • CGLIB创建类的动态代理类的模式:

  • 查找类中所有非 finalpublic 类型的方法定义

  • 将这些方法的定义转换成字节码

  • 将组成的字节码转换成相应的代理的 class 对象

  • 实现 MethodInterceptor 接口,用来处理对代理类上的所有方法的请求 (类似JDK动态代理中的InvocationHandler的作用)

  • 定义一个 Programmer 类,一个 Hacker

/**

  • 开发人员类

*/

public class Programmer {

public void code() {

System.out.println(“开发人员开发程序代码.”);

}

}

/**

  • 黑客类

  • 实现了方法拦截器接口

*/

public class Hacker implements MethodInterceptor {

@Override

最后

Java架构学习技术内容包含有:Spring,Dubbo,MyBatis, RPC, 源码分析,高并发、高性能、分布式,性能优化,微服务 高级架构开发等等。

还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书+2021年最新大厂面试题。
在这里插入图片描述

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
不会有这个方法

  • 如果某个类没有实现接口,那么这个类就不能使用JDK动态代理了

  • CGLIB: Code Generation Library, CGLIB 是一个强大的,高性能,高质量的 Code 生成类库,可以在运行时期扩展 Java 类与实现 Java 接口

  • CGLIB创建类的动态代理类的模式:

  • 查找类中所有非 finalpublic 类型的方法定义

  • 将这些方法的定义转换成字节码

  • 将组成的字节码转换成相应的代理的 class 对象

  • 实现 MethodInterceptor 接口,用来处理对代理类上的所有方法的请求 (类似JDK动态代理中的InvocationHandler的作用)

  • 定义一个 Programmer 类,一个 Hacker

/**

  • 开发人员类

*/

public class Programmer {

public void code() {

System.out.println(“开发人员开发程序代码.”);

}

}

/**

  • 黑客类

  • 实现了方法拦截器接口

*/

public class Hacker implements MethodInterceptor {

@Override

最后

Java架构学习技术内容包含有:Spring,Dubbo,MyBatis, RPC, 源码分析,高并发、高性能、分布式,性能优化,微服务 高级架构开发等等。

还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书+2021年最新大厂面试题。
[外链图片转存中…(img-Y4eyZq3A-1713114922323)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-LMe9ReWy-1713114922324)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 15
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值