首页 > 解决方案 > 扩展最终类或替换它

问题描述

“不,做不到”

已经讨论了很多次,唯一的回答是“不,做不到”。但是伙计们,我们做代码。理论上,只要有一段时间,一切都是可能的。

问题

基本上我正在尝试扩展最终类以更改单个行为。这不能以传统方式完成 - 在编译时使用extend关键字。我已经寻找了其他一些方法:

static class TestClassLoader extends ClassLoader {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (name.equals("java.io.StringReader")) {
            try {
                InputStream is = Test.class.getClassLoader().getResourceAsStream("java/io/StringReader.class");
                byte[] buf = new byte[10000];
                int len = is.read(buf);
                return defineClass(name, buf, 0, len);  // This line throws the SecurityException :(
            } catch (IOException e) {
                throw new ClassNotFoundException("", e);
            }
        }
        return getParent().loadClass(name);
    }
}

所以我们在这里。有什么方法可以用来替换或扩展我的最终课程吗?

PS:我使用 Java 8,因为它SecurityManager比新的 Java 版本更喜欢丑陋的东西 :)


语境

我有一个任务 - 呃,这在 SO 上从未受到好评:我必须为我无法更改的课程编写单元测试。这部分真的很简单,除了一个catch块不能被访问:

Properties props = new Properties();
try
{
    props.load(new StringReader(rules));
} catch (IOException e)
{
    e.printStackTrace();
    throw e;
}

我们老师发给我们的代码覆盖率报告显示他没有测试这个块,所以我们不需要。但我不信任 Java,我确信这个块按预期工作。那我得测试一下!

[注意:因为它不是必需的,它只是发现新的 Java hack 的一种手段,它甚至可能不会计入期末成绩。]


我可以访问的唯一变量是String rules. 然后我应该处理它以使load调用抛出 IOException。load调用抛出 IOException的唯一原因是 StringReader 的流关闭时。这是通过以下ensureOpen方法检查的:

/** Check to make sure that the stream has not been closed */
private void ensureOpen() throws IOException {
    if (str == null)
        throw new IOException("Stream closed");
}

因此,只有 nullString才能使此方法抛出IOException. 然后很容易将 nullString注入rules. 坏消息是StringReader构造函数:

public StringReader(String s) {
    this.str = s;
    this.length = s.length();
}

通过null String. 呃。

我的最后一个想法是更改 the 的值String以使其内部value字段null- 在使用该字段时很容易Modifiers。这不起作用,因为引用而不是值本身应该是nullread由于在以下位置调用方法,这只会引发 NPE StringReader

public int read() throws IOException {
    synchronized (lock) {
        ensureOpen();
        if (next >= length)
            return -1;
        return str.charAt(next++);
    }
}

你有什么主意吗?一个可以为我做这件事的框架?我可以探索一些路径来解决我无用的问题吗?

这个问题与代码测试无关。它是关于如何破解 Java 及其最终类。

标签: javajava-8final

解决方案


Instrumentation API,它允许 Java 软件,即所谓的 Java 代理,在运行时修改类。这就是 Mocking 框架如何执行其工作的方式之一。将其用于单元测试并非不合理。

但请注意,您的代码注入的具体目标是错误的。在代码中

Properties props = new Properties();
try
{
    props.load(new StringReader(rules));
} catch (IOException e)
{
    e.printStackTrace();
    throw e;
}

IOException永远不会被抛出。正如您自己指出的那样,StringReader' 的构造函数不会抛出它,这同样适用于它的read方法。的load方法Properties声明它,因为它必须处理任意Reader实现,但它自己不会产生这样的异常。

因此,在这种情况下,您从 a 中读取String一个新构造的StringReader,它永远不会被抛出,并且注入一个实际上不存在的行为来测试一个永远不会发生的场景是没有实际意义的。

正如您自己也注意到的,它可以抛出 , 的唯一点IOException是 method ensureOpen(),顾名思义,它检查阅读器是否仍然打开。因此,您可以通过简单地调用阅读器来强制抛出行为,close()但是,如上所述,这与被测代码的实际行为不匹配,并且没有必要引发被测异常,这实际上永远不会发生。


推荐阅读