It has been discussed a lot of times and the only reply is "No, it can't be done". But guys we do code. Everything is theoretically possible with some time.
Basically I'm trying to extend a final class to change a single behavior. That can't be done in a traditional way - at compile time with an extend keyword. I have looked for some other ways:
Modifiers of the class, remove the final modifier and extend the class at runtime - that can be done for fields but not for classes;ClassLoader to load a new version of the class - it didn't work because the class I'd like to extend is part of the java package. A ClassLoader fortunately can't load a class of this package because of a SecurityException. Otherwise, that would mess up a lot of securities.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);
}
}
Run Code Online (Sandbox Code Playgroud)
So here we are. Is there any method I could use to replace or extend my final class?
PS: I use Java 8 as the SecurityManager is more keen on ugly things than in newer Java versions :)
I have an assignment - erk, that's never well received on SO: I have to write unit tests for a class that I can't change. This part is really easy except that a catch block can't be accessed:
Properties props = new Properties();
try
{
props.load(new StringReader(rules));
} catch (IOException e)
{
e.printStackTrace();
throw e;
}
Run Code Online (Sandbox Code Playgroud)
The code coverage report our teacher sent us shows that he hasn't tested this block so we don't have to. But I don't trust Java and I would be sure this block works as expected. Then I have to test it!
[Note: as it is not required it is just a mean to discover new Java hacks and it won't probably even count in the final grade.]
The only variable I can access to is the String rules. I should then work on it to make the load call throw an IOException. The only reason for a load call to throw an IOException is when the stream of the StringReader is closed. This is checked by the ensureOpen method:
/** Check to make sure that the stream has not been closed */
private void ensureOpen() throws IOException {
if (str == null)
throw new IOException("Stream closed");
}
Run Code Online (Sandbox Code Playgroud)
Therefore only a null String can make this method throw an IOException. It would then be easy to inject a null String into rules. The bad news is the StringReader constructor:
public StringReader(String s) {
this.str = s;
this.length = s.length();
}
Run Code Online (Sandbox Code Playgroud)
It throws a NPE when passing a null String. Erk.
The last idea I had was to change the value of the String to make its inner value field null - easy when working with the Modifiers of this field. This didn't work because the reference not the value itself should be null; this would only throw a NPE because of the invocation of the read method on the StringReader:
public int read() throws IOException {
synchronized (lock) {
ensureOpen();
if (next >= length)
return -1;
return str.charAt(next++);
}
}
Run Code Online (Sandbox Code Playgroud)
Do you have any idea? A framework that would do this for me? Some path I could explore to solve my useless problem?
This question is not about code testing. It is about how to hack Java and its final classes.
有Instrumentation API,它允许Java软件(即Java代理)在运行时修改类。这是模拟框架执行工作的方式之一。将其用于单元测试并非没有道理。
但是请注意,代码注入的特定目标是错误的。在代码中
Properties props = new Properties();
try
{
props.load(new StringReader(rules));
} catch (IOException e)
{
e.printStackTrace();
throw e;
}
Run Code Online (Sandbox Code Playgroud)
将IOException被永远不会抛出。正如您自己指出的那样,StringReader的构造函数不会抛出它,并且对它的read方法也是如此。声明它的load方法Properties,因为它必须处理任意Reader实现,但它自己不会产生此类异常。
因此,在这种情况下,当您从a读取String具有新构造的时StringReader,将永远不会抛出该异常,并且要注入一个实际上不存在的行为来测试不会发生的情况,这是没有意义的。
正如您自己指出的那样,它唯一可能抛出的地方IOException就是方法ensureOpen(),顾名思义,该方法检查读者是否仍然打开。因此,您可以通过简单地调用close()读取器来强制抛出行为,但是,正如所说的那样,它与被测代码的实际行为不匹配,并且激发被测异常也没有意义,实际上不会发生。