背景说明

今天在运行项目的单元测试覆盖率时,有一处测试报错失败了,但是在正常运行单元测试时却可以通过,报错信息如下:

java.lang.IllegalAccessException: Class com.szyciov.sewx.util.TooltipsTool can not access a member of class com.szyciov.sewx.util.Tooltips with modifiers "private static final transient"
  at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
  at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
  at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
  at java.lang.reflect.Field.set(Field.java:761)
  at com.szyciov.sewx.util.TooltipsTool.init(TooltipsTool.java:18)
  at com.szyciov.sewx.util.Tooltips.<clinit>(Tooltips.java:6)
  at sun.misc.Unsafe.ensureClassInitialized(Native Method)
  at sun.reflect.UnsafeFieldAccessorFactory.newFieldAccessor(UnsafeFieldAccessorFactory.java:43)
  at sun.reflect.ReflectionFactory.newFieldAccessor(ReflectionFactory.java:142)
  at java.lang.reflect.Field.acquireFieldAccessor(Field.java:1088)
  at java.lang.reflect.Field.getFieldAccessor(Field.java:1069)
  at java.lang.reflect.Field.get(Field.java:393)
  at com.szyciov.sewx.util.TooltipsToolTest.testGetKey_success(TooltipsToolTest.java:16)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:497)
  at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
  at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
  at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
  at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
  at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
  at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
  at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
  at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
  at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
  at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
  at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
  at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
  at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
  at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
  at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
  at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
  at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
  at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
  at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

Tooltips类,一系列静态字段,用于引用提示信息,如下:

public class Tooltips {

  static {
    TooltipsTool.init();
  }

  public static String DictNotExist;
  public static String DictTypeEmpty;
  public static String GlobalDatabaseOperateFailed;
  //省略其他类似字段

}

TooltipsTool类,用于对从属性文件中读出提示信息,并对Tooltips类的字段进行初始化,如下:

public class TooltipsTool {

  public static void init() {
    Properties p = getProperties();
    Field[] fields = Tooltips.class.getDeclaredFields();
    for (Field field : fields) {
      try {
        field.set(null, getKey(p, field.getName()));
        //System.out.println(field.getName() + "=" + field.get(null));
      } catch (IllegalArgumentException e) {
        e.printStackTrace();
      } catch (IllegalAccessException e) {
        e.printStackTrace();
      }
    }
  }
}

TooltipsToolTest测试类,主要就是测试Tooltips类的所有字段不为空,如下:

public class TooltipsToolTest {

  @Test
  public void testGetKey_success() {
    Field[] fields = Tooltips.class.getDeclaredFields();
    for (Field field : fields) {
      try {
        String value = (String) field.get(null);
        if (value == null || "".equals(value)) {
          fail(field.getName() + "属性值为空");
        }
      } catch (IllegalArgumentException e) {
        fail(e.getMessage());
      } catch (IllegalAccessException e) {
        fail(e.getMessage());
      }
    }
  }
}

解决方案

一开始我怀疑是那段static的代码背后生成了什么隐藏字段引起,但是后来打印错误信息,发现是叫$jacocoData的字段,百度了一下,发现jacoco(Java Code Coverage)就是EclEmma覆盖率检测工具所使用的技术,那么这个问题肯定就是由EclEmma工具本身引起。

错误提示信息也很明确,这个隐藏字段的修饰符是private static final transient,那么我就想着我只要使用field.isAccessible()来过滤掉这个字段,相关反射操作就不会再操作这个隐藏字段,而我的其他字段都是public的,应该就可以了。

但是经过测试发现,我所有的public字段的field.isAccessible()返回都是false,这是怎么回事?我只能猜测是static修饰符的作用,现在也没有查到相关的理论证明。

最后,换个方式,将Tooltips.class.getDeclaredFields()换成Tooltips.class.getFields()

getDeclaredFields()返回Class中所有的字段,包括私有字段。

getFields() 只返回公共字段,即有public修饰的字段。

完!

发表评论

邮箱地址不会被公开。 必填项已用*标注