通过反射或其他方式重写java final方法?
这个问题是在编写测试用例时出现的。Foo是框架库中的一个类,我没有源代码访问权限通过反射或其他方式重写java final方法?,java,reflection,methods,final,Java,Reflection,Methods,Final,这个问题是在编写测试用例时出现的。Foo是框架库中的一个类,我没有源代码访问权限 public class Foo{ public final Object getX(){ ... } } 我的申请将 public class Bar extends Foo{ public int process(){ Object value = getX(); ... } } 单元测试用例无法初始化,因为由于其他依赖关系,我无法创建Foo对象。当值为null时,Bar
public class Foo{
public final Object getX(){
...
}
}
我的申请将
public class Bar extends Foo{
public int process(){
Object value = getX();
...
}
}
单元测试用例无法初始化,因为由于其他依赖关系,我无法创建Foo对象。当值为null时,BarTest抛出null指针
public class BarTest extends TestCase{
public testProcess(){
Bar bar = new Bar();
int result = bar.process();
...
}
}
有没有一种方法可以使用反射api将getX()设置为非final?或者我应该如何进行测试 如果您的单元测试用例由于其他依赖关系而无法创建Foo,那么这可能表明您没有首先正确地进行单元测试
单元测试意味着在生产代码运行的相同环境下进行测试,因此我建议在测试中重新创建相同的生产环境。否则,您的测试将无法完成。Seb是正确的,只是为了确保您得到问题的答案,除了在本机代码中执行某些操作(我很确定这不会起作用)或在运行时修改类的字节码,以及创建在运行时重写该方法的类之外,我找不到改变方法“最终性”的方法。反射在这里对您没有帮助。您可以创建另一个可以在测试中覆盖的方法:
public class Bar extends Foo {
protected Object doGetX() {
return getX();
}
public int process(){
Object value = doGetX();
...
}
}
然后,您可以在BarTest中重写doGetX。如果
getX()
返回的变量不是final
,您可以使用中介绍的技术通过反射更改private
变量的值
public class Bar extends Foo{
public int process(){
Object value = getX();
return process2(value);
}
public int process2(Object value){
...
}
}
public class BarTest extends TestCase{
public testProcess(){
Bar bar = new Bar();
Mockobj mo = new Mockobj();
int result = bar.process2(mo);
...
}
}
我最终所做的就是上述。这有点难看。。。詹姆斯的解决方案肯定比这好得多 因为这是谷歌“override final method java”的顶级结果之一。我想我会留下我的解决方案。此类展示了使用示例“Bagel”类和免费使用javassist库的简单解决方案:
/**
* This class shows how you can override a final method of a super class using the Javassist's bytecode toolkit
* The library can be found here: http://jboss-javassist.github.io/javassist/
*
* The basic idea is that you get the super class and reset the modifiers so the modifiers of the method don't include final.
* Then you add in a new method to the sub class which overrides the now non final method of the super class.
*
* The only "catch" is you have to do the class manipulation before any calls to the class happen in your code. So put the
* manipulation as early in your code as you can otherwise you will get exceptions.
*/
package packagename;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.Modifier;
/**
* A simple class to show how to use the library
*/
public class TestCt {
/**
* The starting point for the application
*/
public static void main(String[] args) {
// in order for us to override the final method we must manipulate the class using the Javassist library.
// we need to do this FIRST because once we initialize the class it will no longer be editable.
try
{
// get the super class
CtClass bagel = ClassPool.getDefault().get("packagename.TestCt$Bagel");
// get the method you want to override
CtMethod originalMethod = bagel.getDeclaredMethod("getDescription");
// set the modifier. This will remove the 'final' modifier from the method.
// If for whatever reason you needed more than one modifier just add them together
originalMethod.setModifiers(Modifier.PUBLIC);
// save the changes to the super class
bagel.toClass();
// get the subclass
CtClass bagelsolver = ClassPool.getDefault().get("packagename.TestCt$BagelWithOptions");
// create the method that will override the super class's method and include the options in the output
CtMethod overrideMethod = CtNewMethod.make("public String getDescription() { return super.getDescription() + \" with \" + getOptions(); }", bagelsolver);
// add the new method to the sub class
bagelsolver.addMethod(overrideMethod);
// save the changes to the sub class
bagelsolver.toClass();
}
catch (Exception e)
{
e.printStackTrace();
}
// now that we have edited the classes with the new methods, we can create an instance and see if it worked
// create a new instance of BagelWithOptions
BagelWithOptions myBagel = new BagelWithOptions();
// give it some options
myBagel.setOptions("cheese, bacon and eggs");
// print the description of the bagel to the console.
// This should now use our new code when calling getDescription() which will include the options in the output.
System.out.println("My bagel is: " + myBagel.getDescription());
// The output should be:
// **My bagel is: a plain bagel with cheese, bacon and eggs**
}
/**
* A plain bagel class which has a final method which we want to override
*/
public static class Bagel {
/**
* return a description for this bagel
*/
public final String getDescription() {
return "a plain bagel";
}
}
/**
* A sub class of bagel which adds some extra options for the bagel.
*/
public static class BagelWithOptions extends Bagel {
/**
* A string that will contain any extra options for the bagel
*/
String options;
/**
* Initiate the bagel with no extra options
*/
public BagelWithOptions() {
options = "nothing else";
}
/**
* Set the options for the bagel
* @param options - a string with the new options for this bagel
*/
public void setOptions(String options) {
this.options = options;
}
/**
* return the current options for this bagel
*/
public String getOptions() {
return options;
}
}
}
这不是单元测试的目的。单元测试针对的是非常精细的代码单元。你所描述的听起来更像是集成测试或者功能测试;我不知道他工作的具体情况(如其他类、DB等),但有一些测试可以同时处理多个类-例如,如果您使用的是外部库,则可以假设它工作正常,并在测试中使用其所有类别。是的,有“某些测试可以同时处理多个类”. 这些不是单元测试。仍然有用,而且很重要,但它们不是单元测试。“单元”、“冒烟”、“集成”、“黑匣子”、“白匣子”等都是对原始问题的干扰。开发人员想弄清楚他们是否可以用另一种方法代替框架的最终版本。我认为这暴露了一个有缺陷的依赖项,需要进行一些重构。gregturn>实际上,该依赖项是故意表达的,因为框架开发人员不希望任何人覆盖它。是的,你很好地概括了我的意图。thx 8)您指的是面向方面的AOP吗?知道AOP是否可以更改非最终版本的访问权限吗?我不是真的想说AOP,不确定它是否可以做到,我想的更多的是关于。然而,如果有些事情很难做到,可能是有原因的。。。一般来说,困难的事情是不应该做的:-)你是如何做到这一点的?这些类已经定义,并加载到类加载器中。每次我尝试在已经存在的clas上简单地toClass()
,我都会得到如下错误:由以下原因引起:
@xagreaz我犯了一个错误。仅当您将JDK更改为1.8时,它才起作用。注意:使用最新版本的javassist可能适用于JDK 9或10。我并没有真正尝试让它在那些环境中工作;到了main的开头,这个方法就爆炸了。总的来说,这是一次很好的尝试+1@AmirAfghani我在类的顶部注释中解释了为什么会发生这种情况:唯一的“陷阱”是在代码中调用类之前必须进行类操作。因此,请尽可能早地将操作放在代码中,否则会出现异常。
因此,如果在执行类操作之前实例化该类,它将爆炸,因为JVM已经加载了该类。如果你想让它工作,你必须先做类操作。