太长不看版:
- ClassLoader不同,instanceof恒定为false
- ClassLoader不同,子类不能强转为父类。
- PowerMock会使用自定义的MockClassLoader来加载一些类。为什么要这么做,目前没有深究。推测是为了实现@PrepareForTest注解的功能而做这个处理的。
- 使用@PowerMockIgnore注解就可以禁止MockClassLoader加载指定的类(包)
问题描述
在这样的一个单元测试类中,我遇到了一个非常诡异的问题:
/**
* The type XwIcuBizSkeleton4RegOnlyImplTest.
*
* @author Loy
*/
@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*"})
@PrepareForTest(SimpleHttpClient.class)
public class XwIcuBiz4RegOnlyImplTest {
// 略
}
在运行时它会抛出这个异常:
Caused by: java.lang.ClassCastException: sun.management.OperatingSystemImpl cannot be cast to com.sun.management.OperatingSystemMXBean
at com.xxx.SystemDefaultMetricRegistry.appendJvm(SystemDefaultMetricRegistry.java:29)
at com.xxx.SystemDefaultMetricRegistry.(SystemDefaultMetricRegistry.java:17)
直接原因,是代码中使用了这样一行代码来做报警处理:
Monitor
.fatal("ICU.RegisterOnly.REG_RESULT_REJECTED")
.addTag("methodName", "BaseXwIcuBizSkeleton4PollImpl.afterPoll")
.count(1);
这一行代码七拐八绕地,调用到了这一行代码:
final com.sun.management.OperatingSystemMXBean operatingSystemMXBean
= (com.sun.management.OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
这也就是上面异常中所指向的、做了强制类型转换的地方。
但是吊诡的是,异常信息中提到的两个类(sun.management.OperatingSystemImpl
和com.sun.management.OperatingSystemMXBean
),是有继承关系的:OperatingSystemImpl实现了OperatingSystemMXBean接口,为什么子类无法强转为父类?
class OperatingSystemImpl
extends BaseOperatingSystemImpl
implements OperatingSystemMXBean {...}
追查原因
用断点跟踪到抛出异常的那行代码,借助IDEA的Evaluate Expression工具,我首先确定了几件事情:
-
ManagementFactory.getOperatingSystemMXBean()
返回的的确是OperatingSystemImpl 的实例。 -
ManagementFactory.getOperatingSystemMXBean() instanceof com.sun.management.OperatingSystemMXBean
,返回值为false -
ManagementFactory.getOperatingSystemMXBean() instanceof sun.management.OperatingSystemImpl
,返回值为true
然后,我到技术群里问了一嘴:
我:两个class不在同一个classLoader下的话,instanceof是不是一定是false? 大佬:我印象中是
于是我又跑了两个表达式:
-
sun.management.OperatingSystemImpl.class.getClassLoader()
,返回值为null -
com.sun.management.OperatingSystemMXBean.class.getClassLoader()
,返回值为MockClassLoader@xxxx,xxxx是一串数字。这里的MockClassLoader,是org.powermock.core.classloader.MockClassLoader
。
综合上面五个表达式的运行结果,原因就很清楚了:这两个class,一个是在单元测试启动时,由BootStrap ClassLoader加载的;另一个是在运行到一半时,由PowerMock自定义的MockClassLoader加载的。ClassLoader不一样,导致了子类无法强转为父类。
解决方案
那么,要怎么解决这个问题呢?MockClassLoader的javadoc里有这样的说明:
The classloader loads and modified all classes except:
- system classes. They are deferred to system classloader
- classes that locate in packages that specified as packages to ignore with using addIgnorePackage(String…)
渣翻译下:
这个ClassLoader会加载并修改所有的类,除了以下两种类之外:
- 系统类。这些类会推给系统ClassLoader加载,而不会被MockClassLoader加载。
- 通过addIgnorePackage(String...)方法来指定了要忽略某个包,那么这个包下的类都不会被MockClassLoader加载。
所以,如果能告诉PowerMock某个类/包应该被MockClassLoader给ignore掉,这个问题应该就可以得到解决了。所谓七步之内必有解药,在MockClassLoader的包里看一圈,就能看到PowerMockIgnore这个注解,它的javadoc里也说明了:
This annotation tells PowerMock to defer the loading of classes with the names supplied to value() to the system classloader.
也渣翻译下:
这个注解的作用就是告诉PowerMock:在value中声明的类(包)应该推给系统的ClassLoader来处理。
so……在类开头加上@PowerMockIgnore({"javax.management.*", "com.sun.management.*", "sun.management.*"})
就好了。
总结一下
- ClassLoader不同,instanceof恒定为false
- ClassLoader不同,子类不能强转为父类。
PowerMock会使用自定义的MockClassLoader来加载一些类。为什么要这么做,目前没有深究。推测是为了实现@PrepareForTest注解的功能而做这个处理的。 但就是它的这个特性,引发了ClassCastException。 使用@PowerMockIgnore注解就可以禁止MockClassLoader加载指定的类(包)。这样就可以把这些类都交给系统的ClassLoader,从而解决前面说的异常。