背景描述:
使用mockito写单元测试类时报错:it should be loaded by xxxClassLoader.
解决方法:使用sun.misc.Unsave解决某些类需要特定类加载器加载。
上代码:
先写个工具方法
private sun.misc.Unsave getUnsafe(){
Unsafe unsafe = null;
try {
// 反射拿到内部的私有的实例化单例对象
Field field = sun.misc.Unsave.class.getDeclaredField("theUnsafe");
// 暴力获取
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
return unsafe;
}
写测试方法
假设该update方法内部需要调用一个静态的、且只能由jar包中特定类加载器加载的日志方法OkLog.get,
该方法内部会检测是否由正确的类加载器加载,否则报错。
// 伪代码
@Test
public void testUpdate() throws InstantiationException {
// 一些常规操作
// when(..).then...
// when(..).then...
// 关键操作如下
// 因为是静态方法,无法直接when,所以先mock出类
MockedStatic<OakLog> okLogMockedStatic = mockStatic(OkLog.class);
// 是用Unsafe直接构造对象
OkLog o = (OkLog) getUnsafe().allocateInstance(OkLog.class);
// 这里可以when了
when(OkLog.get()).thenReturn(o);
// ...
// 调用被测方法
int i = userService.update(user);
// ...
// 别忘了清除okLogMockedStatic
oakLogMockedStatic.close();
}
后记
unsafw类比较危险,轻易不建议使用
allocateInstance()
方法底层是操作jvm直接分配内存空间并返回一个Object类的实例,该实例的所有字段都为默认值。该方法创建对象时,不会掉用任何构造方法,所以需要注意构造方法中的一些初始化操作并不会执行。
allocateMemory()
方法会直接在堆外内存申请一块连续的内存,使用完毕后需要调用freeMemory()
方法手动回收内存,该方法传入的是long类型的内存地址起始值,getLong方法获取的是对象的起始内存地址
public native void freeMemory(long address);
该方法接收一个long类型的参数address,表示要释放的内存块的起始地址。在调用该方法时,Unsafe类会将指定地址开始的连续内存块释放,这样这些内存块就可以被其他对象或操作使用了。
public native long getLong(Object o, long offset);
该方法接收两个参数,第一个参数o表示要获取的对象,第二个参数offset表示对象内存空间中的偏移量,该方法也就是要获取的字段在对象内存空间中的位置。
注意⚠️
Unsafe类的方法通常是不安全的,因为它们可以直接访问内存空间,可能会导致内存泄漏或内存溢出等问题。因此,在使用Unsafe类时需要格外小心,并遵循相关的安全规范和最佳实践,以避免潜在的安全风险和性能问题。