Java动态代理机制和实现原理详解

大厂高级工程师面试必问系列:Java动态代理机制和实现原理详解 无论是哪家公司,都很重视高并发高可用的技术,重视基础,重视JVM。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。其实我写了这么多,只是我自己的总结,并不一定适用于所有人,相信经过一些面试,大家都会有这些感触。最后我整理了一些面试真题资料,技术知识点剖析教程,还有和广大同仁一起交流学习共同进步,还有一些职业经验的分享。harge()方法定义一个实现两个接口的类ElectricCar,类图如下:/** 阅读详情

代理模式

  • Java动态代理运用了设计模式中常用的代理模式
  • 代理模式:目的就是为其他对象提供一个代理用来控制对某个真实对象的访问
  • 代理类的作用:为委托类预处理消息过滤消息并转发消息进行消息被委托类执行后的后续处理
    通过代理层这一中间层,有效的控制对于真实委托类对象的直接访问,同时又可以实现自定义的控制策略,比如Spring中的AOP机制,这样使得在设计上获得更大的灵活性
  • 代理的基本构成:
  • 代理模式中有 Subject 角色 ,RealSubject 角色和 Proxy 角色:Subject: 负责定义 RealSubject 和 Proxy 角色应该实现的接口RealSubject: 用来真正完成业务服务功能Proxy: 负责将自身的 Request 请求,调用 RealSubject 对应的 request 功能实现业务功能,自身不做真正的业务
  • 静态代理模式:当在代码阶段规定这种代理关系 ,Proxy 类通过编译器编译成 class 文件,当系统运行时,此 class 已经存在这种静态代理模式可以访问无法访问的资源,增强现有的接口业务功能方面有很大的优点.但是大量地使用这种静态代理,会使系统内的类规模增大,并且不易维护由于 Proxy 和 RealSubject 的功能本质上是相同的 ,Proxy 只是中介的作用,这种代理在系统中的存在,会造成代码冗余
  • 为了解决静态代理模式的问题,就有了动态创建Proxy:在运行状态中,需要代理的地方,根据 Subject 和 RealSubject, 动态地创建一个 ProxyProxy使用完之后,就会销毁,这样就可以避免Proxy角色的class在系统中的冗余问题

Java动态代理

  • java.lang.reflect.Proxy:Java动态代理机制的主类提供一组静态方法为一组接口动态的生成对象和代理类
// 该方法用于获取指定代理对象所关联的调用处理器
public static InvocationHandler getInvocationHandler(Object proxy);

// 该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces); 

// 该方法用于判断指定类对象是否是一个动态代理类
public static boolean isProxyClass(Class<?> cl);

// 该方法用于为指定类装载器,一组接口以及调用处理器生成动态代理类实例
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
  • java.lang.reflect.InvocationHandler:调用处理器接口,自定义 invoke 方法用于实现对真正委托类的代理访问
/**
 * 该方法负责集中处理动态代理类上的所有方法调用
 * 
 * @param proxy 代理实例
 * @param method 被调用的方法对象
 * @param args 调用参数
 * @return 返回调用处理器根据三个参数进行预处理或者分派到委托类实例上反射执行的对象
 */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
  • java.lang.ClassLoader:类装载器将类的字节码装载到Java虚拟机即JVM中,并为其定义类对象,然后该类才能被使用Proxy类与普通类的唯一区别就是 :Proxy类字节码是由JVM在运行时动态生成的而不是预存在于任何一个.calss文件中每次生成动态代理类对象时都需要指定一个类装载器对象

Java动态代理机制

Java动态代理创建对象的过程:

  • 通过实现 InvocationHandler 接口创建自己的调用处理器
/* 
 * InvocationHandlerImpl实现了InvocationHandler接口
 * 并能实现方法调用从代理类到委托类的分派转发向委托类实例的引用,用于真正执行分派转发过来的方法调用
 */
 InvocationHandler handler = new InvocationHandlerImpl(...);
  • 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类
// 通过Proxy为包括Interface接口在内的一组接口动态创建代理类的类对象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
  • 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型
// 通过反射从生成的类对象获得构造函数对象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
  • 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入
// 通过构造函数创建动态代理类实例
Interface proxy = (Interface)constructor.newInstance(new Object[] { handler });

为了简化对象创建过程 ,Proxy 类中使用 newInstanceProxy 封装了 步骤2-步骤4, 因此只需要两个步骤即可完成代理对象的创建

// InvocationHandlerImpl实现了InvocationHandler接口,并能实现方法调用从代理类到委托类的分派转发
InvocationHandler handler = new InvocationHandlerImpl(...); 
// 通过 Proxy 直接创建动态代理类的实例
Interface proxy = (Interface)Proxy.newProxyInstance(classLoader, new Class[] { Interface.class }, handler);

Java动态代理注意点

  • 包:代理接口是 public, 则代理类被定义在顶层包 ,package 为空,否则 default, 代理类被定义在该接口所在的包
/*
 * 记录非公共代理接口的包,以便在同一个包中定义代理类
 * 验证所有非公共代理接口是否都在同一个包中 
 */
for (int i =0; i < interfaces.length; i++ ) {
	int flags = interfaces[i].getModifiers();
	if (!Modifier.isPublic(flags)) {
		String name = interfaces[i].getName();
		int n = name.lastIndexOf(".");
		String pkg = ((n == -1) ? "" : name.subString(0, n+1));
		if (proxyPkg == null) {
			proxyPkg = pkg;
		} else if (!pkg.equals(proxyPkg)) {
			throw new IllegalArgumentException("non-public interfaces from different packaes");
		}
	}
}
		if (proxyPkg == null) {
		// 没有使用非公共代理接口代理类的包
		proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
		}
  • 生成的代理类为public final,不能被继承
  • 类名的格式为 :"$ProxyN"N 是逐一递增的数字,代表Proxy是被第N次动态代理生成的代理类对于同一组接口,接口的排列顺序也相同,不会重复创建动态代理类,而是返回一个先前已经创建并缓存了的代理类对象,以此提高效率
synchronized (cache) {
	/*
	 * 不必担心获取到清除掉弱引用的缓存
	 * 因为如果一个代理类已经被垃圾回收,代理类的类加载器也会被垃圾回收
	 * 所以获取到的缓存都是加载到缓存中的映射
	 */
	 do {
	 	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 (InterruptedException e) {
	 				/*
	 				 * 等待生成的代理类有一个极小的限定的时间,因此可以忽略线程在这里的影响
	 				 */
	 			}
	 			continue;
	 		} else {
	 			/*
	 			 * 如果没有这个接口列表已经生成或者正在生成的代理类
	 			 * 需要去生成这些接口的代理类,将这些接口标记为待生成
	 			 */
	 			 cache.put(key, pendingGenerationMarker);
	 			 break;
	 		}
	 	}while (true);
	 }
  • 类继承关系:
    Proxy 类是父类,这个规则适用于所有由 Proxy 创建的动态代理类(这也导致Java动态代理的缺陷,由于Java不支持多继承,所以无法实现对 class 的动态代理,只能对于 Interface 进行代理),该类实现了所有代理的一组接口,所以 Proxy 类能够被安全的类型转换到其所代理的某接口
  • 代理类的根类 java.lang.Object 中的 hashCode(),equals()和().toString 方法同样会被分派到调用处理器 invoke 方法执行

Java动态代理测试

创建一个动态代理类

public class serviceProxy implements InvocationHandler {
	private Object target;
	/**
	 * 绑定委托对象并返回一个代理对象
	 * @param target 真实对象
	 * @return 代理对象
	 */
	public Object bind(Object target, Class[] interfaces) {
		this.target = target;
		// 取得代理对象
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
	}

	/**
	 * 通过代理对象调用方法首先进入这个方法
	 * @param proxy 代理对象
	 * @param Method 方法,被调用方法
	 * @param args 方法的参数
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		/*
		 * JDK动态代理
		 */
		 Object result = null;
		 // 反射方法前调用
		 System.err.println("--反射方法前执行的方法--");
		 // 反射执行方法,相当于调用target.xXX()
		 result = method.invoke(target, args);
		 // 反射方法后调用
		 System.err.println("--反射方法后执行的方法--");
		 return result;
	}
}
  • bind方法:bind方法中的 newProxyInstance 方法,就是生成一个代理对象第一个参数: 类加载器第二个参数: 真实委托对象所实现的接口. 代理对象挂在那个接口下第三个参数: this 代表当前 HelloServiceProxy 类, 也就是使用HelloServiceProxy作为对象的代理
  • invoke方法:invoke 方法有三个参数:第一个 proxy 是代理对象第二个是当前调用那个方法第三个是方法的参数

ProxyTest

public class ProxyTest {
	public static void main(String[] args) {
		HelloServiceProxy proxy = new HelloServiceProxy();
		HelloService service = new HelloServiceImpl();
		// 绑定代理对象
		service = (HelloService) proxy.bind(service, new Class[] {HelloService.class});
		service.sayHello("user");
	}
}

class文件分析

  • Java 编译器编译好 Java 文件后,产生 .class 文件在磁盘中:class 文件是二进制文件,内容是只有 JVM 虚拟机能够识别的机器码JVM 虚拟机读取字节码文件,取出二进制数据加载到内存中,解析 .class 文件内的信息,生成对应的 Class 对象
  • 加载 class 文件字节码到系统内,转换成 class 对象,然后再实例化:定义一个类自定义一个类加载器,用于将字节码转换成 class 对象编译 .class 文件,在程序中读取字节码,然后转换成相应的class对象,再实例化

在运行期生成二进制字节码

  • 在代码中,动态创建一个类:由于 JVM 通过字节码的二进制信息加载类,因此在运行期的系统中,遵循 Java 编译系统组织 .class 文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类
  • 可以使用开源框架在运行时期按照 Java 虚拟机规范对 class 文件的组织规则生成对应的二进制字节码. 比如 ASM,Javassist

ASM

  • ASM 是一个 Java 字节码操控框架:能够以二进制形式修改已有类或者动态生成类ASM 在创建 class 字节码的过程中,操纵的级别是底层 JVM 汇编指令级别ASM 可以直接产生二进制 class 文件,也可以在类被加载入Java虚拟机之前动态改变类行为ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户的要求生成新类
  • 通过ASM生成类的字节码:使用 ASM 框架提供的 ClassWriter 接口,通过访问者模式进行动态创建 class 字节码然后使用 Java 反编译工具 (JD_GUI) 打开硬盘上生成的 类.class 文件查看类信息再使用定义的类加载器将 class 文件加载到内存中,然后创建 class 对象,并且实例化一个对象,调用 code 方法,查看 code 方法中的结果
  • 至此表明: 在代码里生成字节码,并动态地加载成class对象,创建实例是完全可以实现的

Javassist

  • Javassist 是一个开源的分析,编辑和创建 Java 字节码的类库,已经加入 JBoss 应用服务器项目,通过使用 Javassist 对字节码操作为 JBoss 实现动态 AOP 框架:Javassist是JBoss一个子项目,主要优点在于简单快速直接使用Java编码的形式,不需要虚拟机指令,就能改变类的结构或者动态生成类

源码分析

Proxy类

// 映射表: 用于维护类装载器对象到其对应的代理类缓存
private static Map loaderToCache = new WeakHashMap();

// 标记: 用于标记一个动态代理类正在被创建中
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<Object>() {
	   	        	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 当代理类正在被创建时,会临时进行保存 : 接口名字列表:pendingGenerationMarker2.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<Void>() {
				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 角色在执行代理业务的时候,就是在调用真正业务之前或者之后完成一些额外的功能
  • 代理类就是在调用真实角色的方法之前或者之后做一些额外的业务
  • 为了构造具有通用性和简单性的代理类,可以将所有的触发真实角色动作交给一个触发管理器,让这个管理器统一管理触发,这个触发管理器就是 InvocationHandler
  • 动态代理工作的基本工作模式:将方法功能的实现交给 InvocationHandler 角色外接对 Proxy 角色中每一个的调用 ,Proxy 角色都会交给 InvocationHandler 来处理InvocationHandle r则调用具体对象角色的方法
  • 在这种模式中,代理 Proxy 和 RealSubject 应该实现相同的类的 public 方法,有两种方式:一个比较直观的方式: 就是定义一个功能接口,然后让 Proxy 和 RealSubject 来实现这个接口 ( JDK 中的动态代理机制 - Java动态代理机制 )比较隐晦的方式: 通过继承实现 Proxy 继承 RealSubject. 因为Proxy继承自RealSubject, 这样Proxy则拥有了RealSubject的功能 ,Proxy 还可以通过重写 RealSubject 中的方法来实现多态( cglib )

JDK动态代理机制

  • JDK动态代理机制通过接口为RealSubject创建一个动态代理对象:获取 RealSubject 上的所有接口列表确定要生成的代理类类名根据需要实现的接口信息,在代码中动态创建该 Proxy 类的字节码将对应的字节码转换成对应的 class 对象创建 InvocationHandler, 用来处理 Proxy 所有方法调用Proxy 的 class 对象,以创建的 handler 为参数,实例化一个 proxy
  • JDK动态代理实例:定义两个接口Vehicle和RechargeableVehicle接口表示交通工具类,有drive()方法Rechargeable接口表示可充电,有recharge()方法定义一个实现两个接口的类ElectricCar,类图如下:
  • 创建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 的所有的方法功能的实现都统一调用了 InvocationHandler 的 invoke() 方法

CGLIB动态代理机制

  • CGLIB 通过类继承生成动态代理类
  • JDK动态代理类的特点:
    • 某个类必须有实现的接口,而生成的代理类只能代理某个类接口定义的方法. 这样会导致子类实现继承两个接口的方法外,另外实现的方法,在产生的动态代理类中不会有这个方法
    • 如果某个类没有实现接口,那么这个类就不能使用JDK动态代理了
  • CGLIB: Code Generation Library, CGLIB 是一个强大的,高性能,高质量的 Code 生成类库,可以在运行时期扩展 Java 类与实现 Java 接口
  • CGLIB创建类的动态代理类的模式:查找类中所有非 final 的 public 类型的方法定义将这些方法的定义转换成字节码将组成的字节码转换成相应的代理的 class 对象实现 MethodInterceptor 接口,用来处理对代理类上的所有方法的请求 (类似JDK动态代理中的InvocationHandler的作用)
  • 定义一个 Programmer 类,一个 Hacker 类
/**
 * 开发人员类
 */
 public class Programmer {
 	public void code() {
 		System.out.println("开发人员开发程序代码.");
 	}
 }
/**
 * 黑客类
 * 实现了方法拦截器接口
 */
 public class Hacker implements MethodInterceptor {
 	@Override
 	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
 		System.out.println("Hacker what to do.");
 		proxy.invokeSuper(obj, args);
 		System.out.println("Hacker done this.");
 		return null;
 	}
 }
  • 测试类:
public class Test {
		public static void main(String[] args) {
			Programmer programmer = new Programmer();
			Hacker hacker = new Hacker();
			// CGLIB中的加强器,用来创建动态代理
			Enhancer enhancer = new Enhancer();
			// 设置要创建动态代理的类
			enhancer.setSuperclass(programmer.getClass());
			/*
			 * 设置回调
			 * 这里相当于对于代理类上所有方法的调用,都会调用CallBack
			 * 而CallBack则需要实行intercept()方法进行拦截
			 */
			enhancer.setCallBack(hacker);
			Programmer proxy = (Programmer) enhancer.create();
			proxy.code();
		}
}
  • 通过 CGLIB 生成的 class 文件的内容:
public class Programmer EnhancerByCGLIB fa7aa2cd extends Programmer implements Factory {
	/* 
	 * ....
	 */
	 
	 // Enhancer传入的methodInterceptor
	 private MethodInterceptor CGLIB$CALLBACK_0;
	 
	 /*
	  * ...
	  */

	  public final void code() {
	  	MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
	  	if (tmp4_1 == null) {
	  		tmp4_1;
	  		// 若callback不为空,则调用methodInterceptor的intercept()方法
	  		CGLIB$BIND_CALLBACKS(this);
	  	}
	  	if (this.CGLIB$CALLBACK_0 != null)
	  		return;
	  		// 如果没有设置callback回调函数,则默认执行父类的方法
	  		super.code();
	  }
	  /*
	   * ...
	   */
}
Java 动态代理原理图解 (附:2种实现方式详细对比) 动态代理在 Java 中有着广泛的应用,例如:Spring AOP 面向切面编程,Hibernate 数据查询、以及 RPC Dubbo 远程调用等,都有非常多的实际应用。 阅读详情

相关推荐

Java动态代理原理详解:从入门到精通

动态代理通过运行时生成代理类和方法调用拦截机制,实现了代码的灵活增强。JDK动态代理依赖接口,而CGLIB适用于无接口的类。理解其原理后,可结合具体场景选择合适方案,例如在Spring框架中,这两种代理方式被广泛用于AOP实现。关注我,获取更多Java学习和开发干货!💡如需完整代码或进一步探讨,欢迎在评论区留言或访问我的优快云博客!作为Java开发者,持续输出技术总结是提升能力的关键。

weixin_63215868的博客 695

Java 动态原理详解

Java 动态代理是一种在运行时动态地创建代理对象的机制,它可以在不改变原有代码的情况下,对方法进行增强和拦截。动态代理一般采用反射机制来实现,主要包括两种类型:基于接口的动态代理和基于类的动态代理。其中,基于接口的动态代理只能代理实现了某个接口的类,而基于类的动态代理则可以代理任意一个类,不过需要自己手动编写代理类。Java 动态代理是一种非常强大的编程技术,它可以在运行时动态地创建代理对象,并对方法进行增强和拦截等操作。动态代理一般采用反射机制来实现,主要包括基于接口的动态代理和基于类的动态代理。

徐师兄的博客 1485

java高级---->Java动态代理的原理

 Java动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类。代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程中,开发人员还可以按需调整委托类对象及其功能,这是一套非常灵活有弹性的代理框架。下面我们开始动态代理的学习。目录导航  在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class)。 InvocationHandler

2401_86940371的博客 1139

26秋招三分钟快速背诵一个八股-Java动态代理技术的实现原理

代理对象可以在调用真实对象的方法之前和之后,附加自己的操作(如日志、事务、权限检查等),从而实现“增强”或“控制”原始对象的目的,是AOP(面向切面编程)的核心实现方式。它完全依赖于反射机制。你需要实现这个接口,在。方法中编写代理的逻辑。来直接生成代理对象。

m0_47007151的博客 267

Java 中如何实现动态代理:原理与实践

在 Java 中,动态代理是一种非常强大的机制,它允许我们在运行时动态地创建一个类的代理实例,并通过该实例来控制对目标对象方法的访问。这种机制在很多框架中都有广泛的应用,比如 Spring AOP 就是基于动态代理来实现的。和。其中,Proxy类用于创建代理对象,而则是一个接口,我们需要实现它来定义如何处理方法调用。通过本文的介绍,我们了解了 Java 中动态代理的基本原理和实现方法。动态代理在很多场景中都有广泛的应用,比如 AOP 编程、RPC 框架等。

shrgegrb的博客 1281

RNA 8. SCI文章中差异基因表达--热图 (heatmap) 进阶:从数据标准化到多维度注释

本文深入探讨了RNA差异基因表达分析中热图(heatmap)的进阶应用,从数据标准化到多维度注释。详细介绍了FPKM标准化、Z-score转换等关键技术,以及pheatmap包的高级注释功能,帮助研究者制作发表级热图。文章还提供了视觉优化策略和生物学意义衔接的实用建议,助力SCI论文的数据可视化。

weixin_29051149的博客 303

Java进阶 | Proxy动态代理机制详解

在说Java动态代理之前,还是要说一下Jvm加载对象的过程,这个依旧是理解动态代理的基础性原理Java类即源代码程序.java类型文件,经过编译器编译之后就被转换成字节代码.class类型文件,类加载器负责读取字节代码,并转换成java.lang.Class对象,描述类在元数据空间的数据结构,类被实例化时,堆中存储实例化的对象信息,并且通过对象类型数据的指针找到类。所以通过New创建对象,独断其背后很多实现细节,理解上述过程之后,再了解一个常用的设计模式,即代理模式。...

【积累】,是一个长期持续的过程。 3702

深入解析ffmpeg编码错误:avcodec_send_frame()返回-22的排查与修复

本文深入解析FFmpeg编码中avcodec_send_frame()返回-22(AVERROR(EINVAL))错误的根本原因与系统排查方法。核心问题通常在于编码器未正确打开或上下文传递错误。文章提供了从检查编码器查找、确认avcodec_open2()调用,到验证上下文传递的完整修复指南,并附有可运行的代码示例,帮助开发者快速定位并解决这一常见编码障碍。

weixin_28049429的博客 392

详解Java Proxy动态代理机制

简介:大家好,我是程序员枫哥,🌟一线互联网的IT民工、📝资深面试官、🌹Java跳槽网创始人。拥有多年一线研发经验,曾就职过科大讯飞、美团网、平安等公司。在上海有自己小伙伴组建的副业团队,目前业余时间专注Java技术分享,春招/秋招/社招/跳槽,一对一学习辅助,项目接活开发。🌈更多学习内容, 欢迎👏关注👀【文末】微信公众号:IT枫斗者🌟🌟程序员找工作,就上Java跳槽网:www.javatiaocao.com。

IT枫斗者的博客 1345

EGE基础入门篇(一):绘图基础知识

EGE绘图所需要了解的相关基础知识

依稀_yixy的博客 2万+

Tesla V100 Windows 部署实战:从驱动安装到模式切换的避坑指南

本文是一份详细的Tesla V100在Windows系统下的部署实战指南。文章从硬件组装、系统选择讲起,重点阐述了驱动安装的正确方法,并深入剖析了将计算卡从TCC模式切换至WDDM模式的诱惑与潜在陷阱。最终,作者推荐保持默认TCC模式,并提供了使用nvidia-smi命令行等稳定可靠的显卡监控方案,旨在帮助用户避开常见坑点,高效利用这张专业计算卡进行AI计算等任务。

java5的博客 1712

深入理解Java动态代理的实现原理

在理解动态代理之前,我们首先需要了解Java代理模式的基本概念。代理模式是一种常用的设计模式,它为其他对象提供一种代理以控制对这个对象的访问。抽象主题(Subject):声明真实主题和代理的共同接口。真实主题(Real Subject):实现了抽象主题定义的接口,提供了真实的服务。代理(Proxy):也是抽象主题的实现类,它将客户端的请求转发给真实主题,并在必要时进行额外的处理。Java动态代理是一种强大且灵活的代理模式实现方式。

让每一行代码都有改变世界的力量 1279

中小企业办公网络配置综合实验华为设备(NAT+策略路由+OSPF+VLAN)

组网需求: 1、PC1、2、3、4均可以访问internet的lo0:8.8.8.8 2、PC1、PC4通过电信ISP访问internet;PC2、PC3通过联通ISP访问internet; 需求分析: 1、internet路由器模拟公网,8.8.8.8模拟公网IP;电信ISP、联通ISP路由器模拟电信和联通运营商;出口路由为企业出口路由器;这四个路由器直连链路之间运行ospf,配置动态路由,实现互通; 2、出口路由与三层交换直连链路运行ospf,动态同步路由信息; 3、出口路由器内网端口配..

A soul tortured by desire 7125

【JDK动态代理实现原理详解】

首先,我们需要定义一个包含我们想要代理的方法的接口。例如,我们可以定义一个。

qq_33545255的博客 1264

104、母线电容设计与选型

母线电容选型,容值不是最关键的,纹波电流和温度才是。很多炸机事故,不是容值不够,而是纹波电流超标导致过热。选电容时,多花点时间算纹波电流,比盲目加大容值有效得多。另外,别迷信进口品牌。国产电容(如江海、艾华)在常规应用里完全够用,价格只有进口的1/3。但高频特性确实有差距,所以高频去耦一定要用薄膜电容。最后,留个调试技巧:用示波器测母线电容两端的电压纹波,同时用电流探头测电容的纹波电流。如果纹波电流波形有尖峰,说明ESL太大,需要并联高频电容。如果纹波电压是低频波动,说明容值不够。

2401_88858637的博客 50

【项目实战】YOLOV5 +实时吸烟目标检测+手把手教学+开源全部

本原创项目长期更新,旨在完成校园异常行为实时精检测,做到集成+N次开发+优化(不止局限于调包)为止,近期将不断更新以下模型+数据+标注文件+教程。关注博主,Star 一下github,一起开始美妙的目标检测之路吧~~ 文章目录本原创项目长期更新,旨在完成校园异常行为实时精检测,做到集成+N次开发+优化(不止局限于调包)为止,近期将不断更新以下模型+数据+标注文件+教程。关注博主,Star 一下github,一起开始美妙的目标检测之路吧~~一、项目展示二、项目资源共享1:训练图片:香烟图片+吸烟手势+烟雾三、

cv君的博客 13万+

Java动态代理实现全解析:原理、实战与最佳实践

Java动态代理是Java语言核心反射机制的重要应用,它允许在运行时动态生成代理类,用于增强方法行为,广泛应用于AOP、日志、权限控制等场景。本文系统讲解了Java动态代理的原理、实现方式(JDK与CGLIB)、静态与动态代理的对比、常见应用场景、最佳实践与易错点,辅以大量实用代码示例,帮助开发者深入理解和灵活运用动态代理技术。

持续输出Java相关知识 1653

光通量与辐射通量之间的换算

由理论和实验可知存在两种定义: 1W1W1W(瓦特)的频率为540×1012Hz540\times10^{12}Hz540×1012Hz(赫兹)的单色辐射的辐射通量等于683lm(流明)683lm(流明)683lm(流明).。 1lm1lm1lm的频率为540×1012Hz540\times10^{12}Hz540×1012Hz的单色通量等于1683=0.001464W\frac{1}{683}=0.001464W6831​=0.001464W。 从上述定义中我们可以得到什么呢?我们知道光速: c=3×

u011699626的博客 3273

Java反射和动态代理详解和实例

一、反射 JAVA反射机制定义: JAVA反射机制是java程序在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。  反射就是把Java类中的各种成分映射成相应的Java类。 Java反射机制主要提供了以下功能:  1、在运行时判断任意一个对

逍遥飞鹤的专栏 5197

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

java 动态代理实现原理

上篇讲了:java动态代理浅析  这篇讲讲其内部实现原理。 1、相关的类和接口 1.1 java.lang.reflect.Proxy 这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。 Proxy 的静态方法: // 方法 1: 该方法用于获取指定代理对象所关联的调用处理器 static InvocationHandler

爱上在路上 3511

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Java动态代理的实现原理

静态代理 动态代理 基于JDK的动态代理 基于继承的动态代理

熙乎的博客 1万+

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

java的动态代理机制详解

在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的我们的功能,我们更需要学习的是其底层是怎么样的一个原理,而AOP的原理就是java的动态代理机制,所以本篇随笔就是对java的动态机制进行一个回顾。 在java的动态代理机制中,有两个重要的类或接口,一个shiInvocationHandler(Interface)、另一个则是 Proxy(Class),这

weixin_53341657的博客 246
上一篇: 阿里大佬私人珍藏的MySQL笔记,学到就是赚到
下一篇: 2025年最新Java学习路线,走过路过不要错过!
程序员职业指南
博客等级 码龄4年 2138粉丝 960原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值