Автор оригинала: Adrian Precub.
Руководство по приборам Java
1. Введение
В этом учебнике, мы будем говорить о Java-инструментарий API. Это дает возможность добавлять te-код к существующим скомпилированию классов Java.
Мы также поговорим об агентах Java и о том, как мы используем их для инструмента нашего кода.
2. Настройка
На протяжении всей статьи мы будем создавать приложение с использованием приборов.
Наше приложение будет состоять из двух модулей:
- Банкомат приложение, которое позволяет нам снимать деньги
- И Java агент, который позволит нам измерить производительность нашего банкомата путем измерения времени, вложенного тратить деньги
Агент Java изменит карт-код банкомата, что позволит нам измерять время вывода без необходимости изменять приложение банкомата.
Наш проект будет иметь следующую структуру:
com.baeldung.instrumentation base 1.0.0 pom agent application
Прежде чем получить слишком много в детали приборов, давайте посмотрим, что Java агент.
3. Что такое Java-агент
В общем, Java агент просто специально созданный файл банку. Он использует Инструментация API что JVM предоставляет для изменения существующего карт-кода, который загружается в JVM.
Чтобы агент работал, нам необходимо определить два метода:
- премейн – будет статично загружать агента с помощью -javaagent параметра при запуске JVM
- агентмейн – будет динамически загружать агента в JVM с помощью Java Прикрепить API
Интересная концепция, которую следует иметь в виду, заключается в том, что реализация JVM, как Oracle, OpenJDK и другие, может обеспечить механизм для динамического запуска агентов, но это не является требованием.
Во-первых, давайте посмотрим, как мы будем использовать существующий агент Java.
После этого мы посмотрим, как мы можем создать один с нуля, чтобы добавить функциональность нам нужно в нашем карт-коде.
4. Загрузка Java-агента
Чтобы иметь возможность использовать агент Java, мы должны сначала загрузить его.
У нас есть два типа нагрузки:
- статический – использует премейн для загрузки агента с помощью опции -javaagent
- динамический – использует агентмейн для загрузки агента в СПМ с помощью Java Прикрепить API
Далее мы посмотрим на каждый тип нагрузки и объясним, как она работает.
4.1. Статическая нагрузка
Загрузка java-агента при запуске приложения называется статической нагрузкой. Статическая нагрузка изменяет карт-код во время запуска перед выполнением кода.
Имейте в виду, что статическая нагрузка использует премейн метод, который будет работать до запуска кода приложения, чтобы запустить его, мы можем выполнить:
java -javaagent:agent.jar -jar application.jar
Важно отметить, что мы всегда должны ставить – Джаваагент параметр до – банку параметр.
Ниже приведены журналы для нашей команды:
22:24:39.296 [main] INFO - [Agent] In premain method 22:24:39.300 [main] INFO - [Agent] Transforming class MyAtm 22:24:39.407 [main] INFO - [Application] Starting ATM application 22:24:41.409 [main] INFO - [Application] Successful Withdrawal of [7] units! 22:24:41.410 [main] INFO - [Application] Withdrawal operation completed in:2 seconds! 22:24:53.411 [main] INFO - [Application] Successful Withdrawal of [8] units! 22:24:53.411 [main] INFO - [Application] Withdrawal operation completed in:2 seconds!
Мы можем видеть, когда премейн метод побежал и когда MyAtm класс был преобразован. Мы также видим два банкомата снятия транзакций журналы, которые содержат время, необходимое для каждой операции для завершения.
Помните, что в нашем первоначальном приложении у нас не было этого времени завершения транзакции, она была добавлена нашим агентом Java.
4.2. Динамическая нагрузка
Процедура загрузки java-агента в уже запущенную JVM называется динамической нагрузкой. Агент прикрепляется с помощью Java Прикрепите API .
Более сложным сценарием является то, что наше приложение ATM уже запущено в производство и мы хотим динамически добавить общее время транзакций без простоя для нашего приложения.
Давайте напишем небольшой кусочек кода, чтобы сделать именно это, и мы назовем этот класс Агент Загрузчик. Для простоты мы поместим этот класс в файл банки приложений. Таким образом, наше приложение банку файл может как начать нашу заявку, и прикрепить нашего агента к приложению банкомата:
VirtualMachine jvm = VirtualMachine.attach(jvmPid); jvm.loadAgent(agentFile.getAbsolutePath()); jvm.detach();
Теперь, когда у нас есть Агент Загрузчик , мы начинаем наше приложение убедившись, что в десяти секундной паузе между транзакциями, мы будем прикреплять наш агент Java динамически с помощью Агент Загрузчик .
Давайте также добавим клей, который позволит нам либо начать применение или загрузить агента.
Мы назовем этот класс Пусковые и это будет наш основной класс файла банка:
public class Launcher { public static void main(String[] args) throws Exception { if(args[0].equals("StartMyAtmApplication")) { new MyAtmApplication().run(args); } else if(args[0].equals("LoadAgent")) { new AgentLoader().run(args); } } }
Запуск приложения
java -jar application.jar StartMyAtmApplication 22:44:21.154 [main] INFO - [Application] Starting ATM application 22:44:23.157 [main] INFO - [Application] Successful Withdrawal of [7] units!
Присоединение Java-агента
После первой операции мы прикрепляем агент Java к нашему JVM:
java -jar application.jar LoadAgent 22:44:27.022 [main] INFO - Attaching to target JVM with PID: 6575 22:44:27.306 [main] INFO - Attached to target JVM and loaded Java agent successfully
Проверка журналов приложений
Теперь, когда мы прикрепили нашего агента к JVM мы увидим, что у нас есть общее время завершения второй операции вывода банкомата.
Это означает, что мы добавили нашу функциональность на лету, в то время как наше приложение было запущено:
22:44:27.229 [Attach Listener] INFO - [Agent] In agentmain method 22:44:27.230 [Attach Listener] INFO - [Agent] Transforming class MyAtm 22:44:33.157 [main] INFO - [Application] Successful Withdrawal of [8] units! 22:44:33.157 [main] INFO - [Application] Withdrawal operation completed in:2 seconds!
5. Создание Java-агента
Узнав, как использовать агента, давайте посмотрим, как мы можем создать один. Мы посмотрим, как использовать Javassist для изменения карт-кода, и мы объединим это с некоторыми методами API приборов.
Так как java агент использует Java Инструментация API , Прежде чем получить слишком глубоко в создании нашего агента, давайте посмотрим, некоторые из наиболее часто используемых методов в этом API и краткое описание того, что они делают:
- добавитьТрансформер – добавляет трансформатор к приборной двигателю
- getAllLoadedClasses – возвращает массив всех классов, загруженных в настоящее время JVM
- ретрансформироватьКлассы – облегчает инструментарий уже загруженных классов путем добавления карт-кода
- удалитьТрансформер – незарегистрированный поставляемый трансформатор
- переопределитьКлассы – переопределить поставленный набор классов с использованием предоставленных файлов класса, что означает, что класс будет полностью заменен, а не изменен, как с ретрансформироватьклассы
5.1. Создание методов Premain и Agentmain
Мы знаем, что каждому java-агенту нужен по крайней мере один премейн или агентмейн методика. Последний используется для динамической нагрузки, в то время как первый используется для статичной загрузки java-агента в JVM.
Давайте определим оба из них в нашем агенте, чтобы мы могли загрузить этот агент как статично, так и динамически:
public static void premain( String agentArgs, Instrumentation inst) { LOGGER.info("[Agent] In premain method"); String className = "com.baeldung.instrumentation.application.MyAtm"; transformClass(className,inst); } public static void agentmain( String agentArgs, Instrumentation inst) { LOGGER.info("[Agent] In agentmain method"); String className = "com.baeldung.instrumentation.application.MyAtm"; transformClass(className,inst); }
В каждом методе мы объявляем класс, который мы хотим изменить, а затем выкапываем, чтобы преобразовать этот класс с помощью трансформироватьКласс метод.
Ниже приведен код для трансформироватьКласс метод, который мы определили, чтобы помочь нам MyAtm класс.
В этом методе мы находим класс, который мы хотим преобразовать, и используем трансформировать метод. Кроме того, мы добавляем трансформатор в приборный двигатель:
private static void transformClass( String className, Instrumentation instrumentation) { Class> targetCls = null; ClassLoader targetClassLoader = null; // see if we can get the class using forName try { targetCls = Class.forName(className); targetClassLoader = targetCls.getClassLoader(); transform(targetCls, targetClassLoader, instrumentation); return; } catch (Exception ex) { LOGGER.error("Class [{}] not found with Class.forName"); } // otherwise iterate all loaded classes and find what we want for(Class> clazz: instrumentation.getAllLoadedClasses()) { if(clazz.getName().equals(className)) { targetCls = clazz; targetClassLoader = targetCls.getClassLoader(); transform(targetCls, targetClassLoader, instrumentation); return; } } throw new RuntimeException( "Failed to find class [" + className + "]"); } private static void transform( Class> clazz, ClassLoader classLoader, Instrumentation instrumentation) { AtmTransformer dt = new AtmTransformer( clazz.getName(), classLoader); instrumentation.addTransformer(dt, true); try { instrumentation.retransformClasses(clazz); } catch (Exception ex) { throw new RuntimeException( "Transform failed for: [" + clazz.getName() + "]", ex); } }
С этим в сторону, давайте определим трансформатор для MyAtm класс.
5.2. Определение нашего трансформатора
Трансформатор класса должен реализовать КлассФилеТрансформер и внедрить метод преобразования.
Мы будем использовать Явасистская добавить в MyAtm класс и добавить журнал с общим временем транзакции вывода ATW:
public class AtmTransformer implements ClassFileTransformer { @Override public byte[] transform( ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { byte[] byteCode = classfileBuffer; String finalTargetClassName = this.targetClassName .replaceAll("\\.", "/"); if (!className.equals(finalTargetClassName)) { return byteCode; } if (className.equals(finalTargetClassName) && loader.equals(targetClassLoader)) { LOGGER.info("[Agent] Transforming class MyAtm"); try { ClassPool cp = ClassPool.getDefault(); CtClass cc = cp.get(targetClassName); CtMethod m = cc.getDeclaredMethod( WITHDRAW_MONEY_METHOD); m.addLocalVariable( "startTime", CtClass.longType); m.insertBefore( "startTime = System.currentTimeMillis();"); StringBuilder endBlock = new StringBuilder(); m.addLocalVariable("endTime", CtClass.longType); m.addLocalVariable("opTime", CtClass.longType); endBlock.append( "endTime = System.currentTimeMillis();"); endBlock.append( "opTime = (endTime-startTime)/1000;"); endBlock.append( "LOGGER.info(\"[Application] Withdrawal operation completed in:" + "\" + opTime + \" seconds!\");"); m.insertAfter(endBlock.toString()); byteCode = cc.toBytecode(); cc.detach(); } catch (NotFoundException | CannotCompileException | IOException e) { LOGGER.error("Exception", e); } } return byteCode; } }
5.3. Создание файла манифеста агента
Наконец, для того, чтобы получить работающего java-агента, нам понадобится файл манифеста с несколькими атрибутами.
Таким образом, мы можем найти полный список явных атрибутов в Инструментный пакет официальная документация.
В окончательном файле банки агента Java мы добавим следующие строки в файл манифеста:
Agent-Class: com.baeldung.instrumentation.agent.MyInstrumentationAgent Can-Redefine-Classes: true Can-Retransform-Classes: true Premain-Class: com.baeldung.instrumentation.agent.MyInstrumentationAgent
Наш приборный агент Java завершен. Чтобы запустить его, пожалуйста, обратитесь к загрузке раздела Java Agent этой статьи.
6. Заключение
В этой статье мы говорили об JAVA Instrumentation API. Мы рассмотрели, как загрузить java-агент в JVM как статично, так и динамически.
Мы также посмотрели, как мы будем идти о создании нашего собственного агента Java с нуля.
Как всегда, полную реализацию примера можно найти более на Github .