переопределить финальные методы Java с помощью отражения или других средств? - PullRequest
10 голосов
/ 14 апреля 2009

Этот вопрос возникает при попытке написать контрольные примеры. 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 () не финальным? или как мне пройти тестирование?

Ответы [ 6 ]

19 голосов
/ 16 ноября 2016

Поскольку это был один из лучших результатов для "переопределить окончательный метод Java" в Google. Я думал, что оставлю свое решение. Этот класс демонстрирует простое решение с использованием примера класса «Бублик» и бесплатной библиотеки 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;
        }
    }
}
5 голосов
/ 14 апреля 2009

вы можете создать другой метод, который вы можете переопределить в своем тесте:

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

тогда вы можете переопределить doGetX в BarTest.

2 голосов
/ 14 апреля 2009

Seb является правильным, и просто для того, чтобы вы получили ответ на свой вопрос, если не делать что-то в нативном коде (и я уверен, что это не сработает) или изменять байт-код класса во время выполнения, и создавать В классе, который переопределяет метод во время выполнения, я не вижу способа изменить «окончательность» метода. Отражение тебе здесь не поможет.

0 голосов
/ 15 апреля 2009
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);
    ...
  }
}

то, что я сделал в конце концов, было выше. это немного некрасиво ... решение Джеймса определенно намного лучше, чем это ...

0 голосов
/ 14 апреля 2009

Если переменная, возвращаемая getX(), не равна final, вы можете использовать методику, описанную в Каков лучший способ модульного тестирования частных методов? для изменения значения переменной private с помощью Reflection.

0 голосов
/ 14 апреля 2009

Если ваш модульный тестовый пример не может создать Foo из-за других зависимостей, это может быть признаком того, что вы не проводите свой модульный тест прямо с самого начала.

Модульные тесты предназначены для тестирования в тех же условиях, в которых будет работать производственный код, поэтому я бы предложил воссоздать ту же рабочую среду внутри ваших тестов. В противном случае ваши тесты не будут завершены.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...