通过反射或其他方式覆盖java最终方法?

发布于 2024-07-16 22:29:48 字数 571 浏览 12 评论 0 原文

这个问题是在尝试编写测试用例时出现的。 Foo 是框架库中的一个类,我无权访问它的源代码。

public class Foo{
  public final Object getX(){
  ...
  }
}

我的应用程序将

public class Bar extends Foo{
  public int process(){
    Object value = getX();
    ...
  }
}

单元测试用例无法初始化,因为由于其他依赖项我无法创建 Foo 对象。 BarTest 抛出一个空指针,因为值为空。

public class BarTest extends TestCase{
  public testProcess(){
    Bar bar = new Bar();        
    int result = bar.process();
    ...
  }
}

有没有办法使用反射 api 将 getX() 设置为非最终? 或者我应该如何进行测试?

This question arise while trying to write test cases. Foo is a class within the framework library which I dont have source access to.

public class Foo{
  public final Object getX(){
  ...
  }
}

my applications will

public class Bar extends Foo{
  public int process(){
    Object value = getX();
    ...
  }
}

The unit test case is unable to initalize as I can't create a Foo object due to other dependencies. The BarTest throws a null pointer as value is null.

public class BarTest extends TestCase{
  public testProcess(){
    Bar bar = new Bar();        
    int result = bar.process();
    ...
  }
}

Is there a way i can use reflection api to set the getX() to non-final? or how should I go about testing?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

生死何惧 2024-07-23 22:29:49

因为这是谷歌中“覆盖最终方法 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;
        }
    }
}

As this was one of the top results for "override final method java" in google. I thought I would leave my solution. This class shows a simple solution using the example "Bagel" class and a free to use javassist library:

/**
 * 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;
        }
    }
}
花心好男孩 2024-07-23 22:29:49

您可以创建另一个可以在测试中重写的方法:

public class Bar extends Foo {
  protected Object doGetX() {
    return getX();
  }
  public int process(){
    Object value = doGetX();
    ...
  }
}

然后,您可以在 BarTest 中重写 doGetX。

you could create another method which you could override in your test:

public class Bar extends Foo {
  protected Object doGetX() {
    return getX();
  }
  public int process(){
    Object value = doGetX();
    ...
  }
}

then, you could override doGetX in BarTest.

許願樹丅啲祈禱 2024-07-23 22:29:49

Seb 是正确的,只是为了确保您得到问题的答案,而不是在本机代码中执行某些操作(我很确定这行不通)或在运行时修改类的字节码,并创建该类在运行时覆盖该方法,我看不到改变方法“最终性”的方法。 反思在这里对你没有帮助。

Seb is correct, and just to ensure that you get an answer to your question, short of doing something in native code (and I am pretty sure that would not work) or modifying the bytecode of the class at runtime, and creating the class that overrides the method at runtime, I cannot see a way to alter the "finalness" of a method. Reflection will not help you here.

最初的梦 2024-07-23 22:29:49

如果您的单元测试用例由于其他依赖项而无法创建 Foo,这可能表明您一开始就没有正确进行单元测试。

单元测试旨在在与生产代码运行相同的环境下进行测试,因此我建议在测试中重新创建相同的生产环境。 否则,您的测试将不完整。

If your unit test case can't create Foo due to other dependencies, that might be a sign that you're not making your unit test right in the first place.

Unit tests are meant to test under the same circumstances a production code would run, so I'd suggest recreating the same production environment inside your tests. Otherwise, your tests wouldn't be complete.

烟织青萝梦 2024-07-23 22:29:49

如果 getX() 返回的变量不是 final,您可以使用 单元测试私有方法的最佳方法是什么? 用于更改 private 的值通过反射变量。

If the variable returned by getX() is not final you can use the technique explained in What’s the best way of unit testing private methods? for changing the value of the private variable through Reflection.

往事随风而去 2024-07-23 22:29:49
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);
    ...
  }
}

我最终所做的是上述。 它有点难看......詹姆斯的解决方案肯定比这个好得多......

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

what i did eventually was the above. it is a bit ugly... James solution is definitely much better than this...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文