Страницы

Поиск по вопросам

воскресенье, 22 декабря 2019 г.

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

#java


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


Ответы

Ответ 1



Если есть класс и в нем поле типа 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.

Ответ 2



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

Комментариев нет:

Отправить комментарий