从第 3 方移除最终修饰符 class

Remove final modifier from 3rd party class

我需要测试编写一个 JUnit 测试来测试以下行:

CSVRecord csvRecord = csvReader.readCsv(filename);

org.apache.commons.csv 中的 CSVRecord 是最终的 class。如果我尝试使用 EasyMock 对此进行测试,我会收到以下错误:

java.lang.IllegalArgumentException: Cannot subclass final class pathname.FinalClass
at org.easymock.cglib.proxy.Enhancer.generateClass(Enhancer.java:565)
at org.easymock.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
at ...

所以我需要从 CSVRecord 中分离 "final" 修饰符。我用 javassist 试过了。但是,我 运行 出错了。看看这个简约的例子:

public class MyTestClass extends EasyMockSupport {

    @Mock
    private MockedClass mockedClass;

    @TestSubject
    private MyClass classUnderTest = new AmountConverter();

    @Test
    public void testName() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.get(FinalClass.class.getName());
        ctClass.defrost();
        removeFinal(ctClass);
        FinalClass finalClass = (FinalClass) EasyMock.createMock(ctClass.toClass());
        expect(mockedClass.foo()).andReturn(finalClass);

        replayAll();

        classUnderTest.foo();
    }

        static void removeFinal(CtClass clazz) throws Exception {
        int modifiers = clazz.getModifiers();
        if(Modifier.isFinal(modifiers)) {
            System.out.println("Removing Final");
            int notFinalModifier = Modifier.clear(modifiers, Modifier.FINAL);
            clazz.setModifiers(notFinalModifier);
        }
    }
}

public class MyClass {

    @Inject
    private MockedClass mockedClass;

    public void foo() {
        mockedClass.foo();
    }

    class MockedClass {

        FinalClass foo() {
            return null;
        }

    }
}

并且在它自己的 class 文件中

public final class FinalClass {

}

我收到以下错误

javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name: "pathname/FinalClass"
at javassist.ClassPool.toClass(ClassPool.java:1099)
at javassist.ClassPool.toClass(ClassPool.java:1042)
at javassist.ClassPool.toClass(ClassPool.java:1000)
at javassist.CtClass.toClass(CtClass.java:1224)
...

我认为最好使用 EasyMock 的替代品而不是使用 Javassist。

您可以使用支持 mocking final 的 Mockito 2 类:Mock the unmockable: opt-in mocking of final classes/methods

另一种选择是使用 PowerMock:https://github.com/powermock/powermock/wiki/MockFinal

您不能通过这种方式更改已加载 class 的定义。

问题是构造 FinalClass.class.getName() 或更具体的 class 文字 FinalClass.class 确实已经加载 class 以生成关联的 Class 对象,加载的 class.

的运行时表示

假设您在此之前没有以任何其他方式使用 class,您只需将代码更改为

ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("qualified.name.of.FinalClass");
ctClass.defrost();
removeFinal(ctClass);
FinalClass finalClass = (FinalClass) EasyMock.createMock(ctClass.toClass());

在创建运行时表示之前更改 class 的定义。