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

Posted java构架师

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了大厂高级工程师面试必问系列:Java动态代理机制和实现原理详解相关的知识,希望对你有一定的参考价值。

代理模式

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

  • Javassis 是一个开源的分析,编辑和创建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 当代理类正在被创建时,会临时进行保存 : 接口名字列表: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, 以上是关于大厂高级工程师面试必问系列:Java动态代理机制和实现原理详解的主要内容,如果未能解决你的问题,请参考以下文章

大厂Java初级开发工程师!!!面试必问项之Set实现类:TreeSet

彻底剖析JVM类加载机制系列,面试必问!

大厂面试官必问的Mysql锁机制

Android面试6家一线大厂,这个问题是必问!

大厂面试必问!程序员,我教你玩转Tomcat监控管理

备战秋招冲击大厂Java面试题系列—JVM及垃圾回收机制