java - Java - 扩展final类或替换它

140 5

它已经被多次讨论了,唯一的答案是"不,不可能",但是我们是写代码的人,从理论上讲,一切都有可能。

问题

我试图扩展一个final类来改变一个行为,这不能用传统方式完成-在编译时使用extend关键字,我已经找了一些其他的方法:

  • 创建一个只适用于接口的代理;
  • 编辑类的Modifiers,移除final修饰符,并且在运行时扩展类,但是可以对字段执行这些操作:
  • 使用自定义ClassLoader加载类的新版本-,因为要扩展的类是java包的一部分,所以它不能工作,幸运的是,ClassLoader无法加载这个包的类,因为它有SecurityException ,否则会造成很多混乱,

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);


 }


}



有什么方法可以用来替换或扩展final类?

Context

我必须为一个无法改变的类编写单元测试,除了catch块不能访问外,此部分非常容易:


Properties props = new Properties();


try


{


 props.load(new StringReader(rules));


} catch (IOException e)


{


 e.printStackTrace();


 throw e;


}




我可以访问的唯一变量是Stringrules ,然后,我应该对它进行处理,使load调用抛出IOException ,这由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");


}



因此,只有空String才能使此方法引发IOException ,然后,将空String注入rules很容易,坏消息是StringReader构造函数:


public StringReader(String s) {


 this.str = s;


 this.length = s.length();


}



当传递nullString时它抛出一个NPE ,

我的最后一个想法是改变String的值使它内部value字段null -在使用该字段的Modifiers时很容易,因为引用本身不是null,所以,这不能工作; 因为在StringReader上调用read方法,这只会引发NPE :


public int read() throws IOException {


 synchronized (lock) {


 ensureOpen();


 if (next >= length)


 return -1;


 return str.charAt(next++);


 }


}



有什么办法吗?

时间: 原作者:

145 1

有一个InstrumentationAPI,它允许Java软件(称为Java代理)在运行时修改类,把它用于单元测试是不合理的。

但是请注意,代码注入的特定目标是错误的。 在代码中


Properties props = new Properties();


try


{


 props.load(new StringReader(rules));


} catch (IOException e)


{


 e.printStackTrace();


 throw e;


}



IOException从不被抛出,你已经注意到,StringReader的构造函数并不抛出它,同样也适用于它的read方法,Propertiesload方法声明它,因为它必须处理任意的Reader实现,但是,它不会自己生成这样的异常。

所以,在这个场景中,从String读取一个新的StringReader,它将不会被抛出,并且注入一个不存在的方案。

正如你自己注意到的那样,它可以抛出IOException的唯一方法是ensureOpen(),如名称所示,因此,你可以调用close(),这与测试中的代码的实际行为不匹配。

原作者:
59 1

可能的解决方案之一是使用Aspect。

你可以在final类方法的执行过程中创建一个Aspect和pointcut,你可以在aspect实现你的行为。

原作者:
80 0

你说,修改或替换JRE不是你的选择,那么唯一的解决方法就是修改字节码,

1)使用PowerMock它的一个重要部分是修改字节代码MockClassLoader的类加载器,

2)在加载字节代码之前修改它,有不同的工具可以使用: AspectJ (当你拥有源代码,并且编译它时),ASM (你还可以修改现有的字节代码),其他方法的一个重要区别是在编译期间使用这种修改类,编译器不知道修改类的原始字节代码。

3)在加载时修改字节代码,你可以使用AsppectJ,spring AOP,ASM ,最强大的是ASM,但是,它也需要更多的努力,AspectJ是强大的,并且拥有与ASM相比的高级API ,和AspectJ相比,spring AOP相当有限,但是在某些情况下它的用法更简单。

4)你可以结合不同的方法,然后你可以使用CGLIB修改类的行为,如你所需,但是使用CGLIB你可以给你的类添加新的方法。

原作者:
...