Java.lang.System.currentTimeMillis() заменить метод

Помимо перекомпиляции rt.jar есть ли способ заменить вызов currentTimeMillis() одним из моих?

1 # Правильный способ сделать это - использовать объект Clock и абстрактное время.

Я знаю это, но мы будем запускать код, разработанный бесконечным числом разработчиков, которые не реализовали Clock, или сделали свою собственную реализацию.


2 # Используйте макет, например JMockit, чтобы издеваться над этим классом.

Даже если это работает только с Hotspot disabled -Xint, и у нас есть успех, используя приведенный ниже код, он не "сохраняется" в внешних библиотеках. Это означает, что вам придется обманывать его повсюду, что, поскольку код находится вне нашего контроля, не представляется возможным. Весь код под main() возвращает 0 milis (как из примера), но new DateTime() вернет фактические системные миллионы.

    @MockClass(realClass = System.class)
    public class SystemMock extends MockUp<System> { 
        // returns 1970-01-01   
        @Mock public static long currentTimeMillis() { return 0; }
    }

3 # Повторно объявить System при запуске с помощью -Xbootclasspath/p (отредактировано)

Хотя возможно, и хотя вы можете создавать/изменять методы, тот, о котором идет речь, объявляется как public static native long currentTimeMillis();. Вы не можете изменить его декларацию, не врываясь в собственный запатентованный и собственный код Sun, что сделало бы это упражнение обратной инженерии и вряд ли устойчивым. Все последние аварии SUN JVM со следующей ошибкой:

    EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00000, pid=4668, tid=5736  

4 # Использовать собственный класс ClassLoader (новый тест, предложенный в комментариях)

В то время как тривиально заменить систему CL с помощью -Djava.system.class.loader JVM фактически загружает пользовательский классLoader, прибегая к классу classLoader по умолчанию, и система даже не толкается через пользовательский CL.

    public class SimpleClassLoader extends ClassLoader {
        public SimpleClassLoader(ClassLoader classLoader) {
            super(classLoader);
        }

        @Override 
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            return super.loadClass(name);
        }   
    }

Мы видим, что java.lang.System загружается из rt.jar с помощью java -verbose:class

Line 15: [Loaded java.lang.System from C:\jdk1.7.0_25\jre\lib\rt.jar]

У меня заканчиваются варианты.
Есть ли какой-то подход, который мне не хватает?

Ответ 1

Вы можете использовать компилятор/ткач AspectJ для компиляции/сглаживания проблемного кода пользователя, заменив вызовы на java.lang.System.currentTimeMillis() с помощью собственного кода. Следующий аспект просто сделает это:

public aspect CurrentTimeInMillisMethodCallChanger {

    long around(): 
       call(public static native long java.lang.System.currentTimeMillis()) 
       && within(user.code.base.pckg.*) {
         return 0; //provide your own implementation returning a long
    }
}

Ответ 2

Я не уверен на 100%, если я что-то наблюдаю здесь, но вы можете создать свой собственный класс System следующим образом:

public static class System {
    static PrintStream err = System.err;
    static InputStream in = System.in;
    static PrintStream out = System.out;

    static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) {
        System.arraycopy(src, srcPos, dest, destPos, length);
    }

    // ... and so on with all methods (currently 26) except `currentTimeMillis()`

    static long currentTimeMillis() {
        return 4711L; // Your application specific clock value
    }
}

чем импортировать свой собственный класс System в каждый файл java. Реорганизация импорта в Eclipse должна сделать трюк. И чем все java файлы должны использовать ваш конкретный класс System для вашего приложения.

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

Ответ 3

Как обсуждалось в комментариях, возможно, что вариант №3 в исходном вопросе действительно сработал, успешно заменив класс System по умолчанию.

Если это так, то код приложения, который вызывает currentTimeMillis(), будет вызывать замену, как и ожидалось.

Возможно, неожиданно, основные классы, такие как java.util.Timer, также получат замену!

Если все вышеизложенное верно, то основной причиной сбоя может быть успешная замена класса System.

Чтобы протестировать, вместо System можно заменить копию, функционально идентичную оригиналу, чтобы увидеть, исчезли ли сбои.

К сожалению, если этот ответ окажется правильным, похоже, у нас есть новый вопрос.:) Это может выглядеть так:

"Как вы предоставляете измененные классы System.currentTimeMillis() для приложений, но оставьте реализацию по умолчанию для основных классов?"

Ответ 4

Я попытался использовать javassist, чтобы удалить родной currentTimeMills, добавить чистый java файл и загрузить его с помощью bootclasspath/p, но я получил такое же нарушение доступа к исключению, как и вы. я считаю, что, вероятно, из-за собственного метода registerNatives, вызываемого в статическом блоке, но это действительно слишком много, чтобы разобрать собственную библиотеку.

поэтому вместо изменения System.currentTimeMills, как насчет изменения кода пользователя? если код пользователя уже скомпилирован (у вас нет исходного кода), мы можем использовать такие инструменты, как findbugs, для определения использования currentTimeMillis и отклонения кода (возможно, мы можем даже заменить вызов currentTimeMills собственной реализацией).