Как на java подменить код библиотеки?

189
26 апреля 2018, 11:32

Есть библиотека которая использует HashMap из стандартной библиотеки. Как можно сделать так, чтобы она использовала мой HashMap? Код библиотеки трогать нельзя.

Answer 1

Есть несколько разных способов, а сама ситуация довольно тривиальна. У вас нет возможности получить исходники и собрать вручную новый jar-ник, а обычно никто просто не хочет сам пересобирать проект, подключать его как модуль или как-то ещё терять возможность использовать зависимость, прописав одну строчку в конфигурационном файле. Один из способов я опишу ниже, им в случае крайней необходимости пользуюсь сам и, по моему скромному мнению, он не выглядит как костыль.

Способ заключается в простом: если класс не final и от него можно отнаследоваться таким образом, чтобы можно было безболезненно изменить все нужные части и иметь возможность дальше прокидывать «ребёнка» под видом инстанса самого класса, то так и стоит поступить. Просто экстендимся, переопределяем все нужные части и так далее. Если поля private, или всё жёстко друг с другом связано — можно перейти к пункту 2. Но нередко и такой способ может помочь. В этом случае вы не теряете возможность получения обновлений используемой библиотеки, не лезете в её код, не пользуетесь рефлексией и прочими радостями жизни Java-разработчика.

Второй способ чуть сложнее. Логично, что даже если вы назовёте класс также, прокинуть его под видом класса библиотеки не получится, и вы наткнётесь, что встроить свою реализацию нужного класса не получится просто так. В этом случае вы просто копируете нужный класс, производите нужные манипуляции (меняете тип полей, входные параметры и вообще любую логику) и берёте все классы, которые жёстко привязывают к использованию изменённого класса. Переписываете и их, меняя лишь тип параметра в нужных местах. Если библиотека написана нормально, то уровень абстракции должен позволить поменять не более пары-тройки классов кроме непосредственно самого, который вы меняли. И то, в них придётся поменять по паре строк кода.

Мне приходилось заниматься подобным не раз, когда хотелось встроить свою сложную или специфичную логику в код библиотеки, но это нельзя было сделать просто прокинув куда-то свой класс, имплементящий нужный интерфейс, а клонить её всю и менять её сорцы — последнее, к чему я бы прибегнул, особенно, если библиотека немаленькая и часто обновляется.

Answer 2
  • Если есть класс и в нем поле типа HashMap:
    Тогда перед началом использования библиотеки воспользоваться Reflection API и просетать свою реализацию.
  • Если HashMap создаются как локальные переменные методов:
    Здесь можно воспользоваться Java Agent

    public class HashMapTransformationAgent {
    public static void premain(String agentArgument, Instrumentation instrumentation) {
        System.out.println("I am javaagent Counter");
        instrumentation.addTransformer(new HashMapClassTransformer());
    }
    }  
    

    Полезная статья про Java Agent https://habr.com/post/230239/
    "Он будет запущен еще перед запуском вашего приложения. Сам агент это отдельное приложение которое предоставляет доступ к механизму манипуляции байт-кодом (java.lang.instrument) в runtime." В ClassFileTransformer можно использовать библиотеки Javaassist или Java ASM. Java ASM более низкоуровневая http://www.baeldung.com/java-asm.
    Пример с Javaassist, замеряем время работы метода put.

    public class HashMapClassTransformer implements ClassFileTransformer {
    private static int count = 0;
    @Override
    public byte[] transform(ClassLoader loader,
                            String className,
                            Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {
        System.out.println("load class: " + className.replaceAll("/", "."));
        System.out.println(String.format("loaded %s classes", ++count));
        String ctClassName = className.replaceAll("/", ".");
        try {
            if(ctClassName.equals("java.util.HashMap")) {
                // Javassist
                try {
                    ClassPool cp = ClassPool.getDefault();
                    CtClass cc = cp.get(ctClassName);
                    CtMethod m = cc.getDeclaredMethod("put");
                    m.addLocalVariable("elapsedTime", CtClass.longType);
                    m.insertBefore(
                            "elapsedTime = System.currentTimeMillis();"
                                    //+ "long a = 5000l;"
                                    + "Thread.sleep(1000l);"
                    );
                    m.getMethodInfo().getAttributes();
                    m.insertAfter(
                            "{elapsedTime = System.currentTimeMillis() - elapsedTime;"
                                    + "System.out.println(\"Method Executed in ms: \" + elapsedTime);}"
                    );
                    byte[] byteCode = cc.toBytecode();
                    cc.detach();
                    return byteCode;
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        } catch (Exception e){
            System.out.println("Error: " + e.getMessage());
        }
        return classfileBuffer;
    }
    }  
    

    Но минус данного подхода в том, что теперь весь ваш код, а не только сторонняя библиотека, будет использовать кастомный HashMap.

READ ALSO
Не создается бинарное дерево

Не создается бинарное дерево

В строке присвоения значения rootdata возникает ошибка NullPointerExeption, не могу понять почему

239
java, write to file, unicode

java, write to file, unicode

У меня возникла проблема

174
не работает margin на реальном устройстве

не работает margin на реальном устройстве

Проблема: в эмуляторе студии margin работает, а вот на планшете нет, в чем может быть заковырка?

187
Как узнать &ldquo;data-key&rdquo; value из тега &ldquo;tr&rdquo; используя Selenium Webdriver + Java?

Как узнать “data-key” value из тега “tr” используя Selenium Webdriver + Java?

Мне нужно узнать "data-key" элемента в таблице("Some name" на скриншоте) который завернут в тег "td"Можно это сделать используя Selenium WD + Java?

137