Переопределение класса Dynami c во время выполнения - PullRequest
0 голосов
/ 05 апреля 2020

Я недавно играл с java API инструментовки и байтовым приятелем. Моя цель - изменить поведение уже загруженного класса. Мне удалось изменить существующий метод, но мне не удалось добавить совершенно новый.

Первый подход:

public static void agentmain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
    System.out.println(("[Agent] In agentmain/premain method"));

    Class<?> clazz = Class.forName("com.example.instrumentation.agent.AppService");

    inst.addTransformer(new AppServiceTransformer(), true);
    inst.retransformClasses(clazz);
}
public class AppServiceTransformer implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        byte[] byteCode = null;
        System.out.println("Transformation");
        try {
            byteCode = new ByteBuddy()
                .redefine(classBeingRedefined)
//                .defineMethod("getExperimental", String.class, Opcodes.ACC_PUBLIC)
//                .intercept(FixedValue.value("This is a message from the ByteBuddy hacker !!!"))
                .method(named("getAnswer"))
                .intercept(FixedValue.value("Service has been hacked :)"))
                .make()
                .getBytes();
        } catch (Throwable e) {
            System.err.println(e);
            System.err.println("Failed to transform");
        }
        return byteCode;
    }
}

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

Exception in thread "main" com.sun.tools.attach.AgentInitializationException: Agent JAR loaded but agent failed to initialize

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

Failed to transform
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:567)
    at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:513)
    at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:525)
Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method

Второй подход:

    public static void agentmain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
        System.out.println(("[Agent] In agentmain/premain method"));

        new AgentBuilder.Default()
            .type(named("com.jarek.example.instrumentation.agent.AppService"))
            .transform(new AgentBuilder.Transformer() {
                @Override
                public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
                    System.out.println("Entered transform");
                    return builder.method(named("getAnswer"))
                        .intercept(FixedValue.value("Service has been hacked :)"))
                        .defineMethod("getExperimental", String.class, Opcodes.ACC_PUBLIC)
                        .intercept(FixedValue.value("This is experimental feature"));
                }
            })
            .with(AgentBuilder.RedefinitionStrategy.REDEFINITION)
            .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
            .installOn(inst);
    }

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

Третий подход:

    public static void premain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
        System.out.println(("[Agent] In agentmain/premain method"));

        Class<?> clazz = Class.forName("com.example.instrumentation.agent.AppService");

        ByteBuddyAgent.install();
        new ByteBuddy()
            .redefine(clazz)
//            .defineMethod("getExperimental", String.class, Opcodes.ACC_PUBLIC)
//            .intercept(FixedValue.value("This is a message from the ByteBuddy hacker !!!"))
            .method(named("getAnswer"))
            .intercept(FixedValue.value("Service has been hacked :)"))
            .make()
            .load(clazz.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
}

Этот случай работает только для основного агента для изменения существующего метода , При попытке добавить новый метод выдается

Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method

. При подключении этого агента к уже запущенному приложению ничего не происходит.

  1. Кто-нибудь знает, возможно ли это добиться того, что я тоже пытался?
  2. Какой из этих подходов верен и почему они ведут себя по-разному?
  3. Как это так? Возможно, что мы можем изменить поведение класса, который уже загружен в виртуальную машину? Имеет ли этот механизм какое-либо конкретное c имя? Я попытался найти некоторую информацию о inte rnet, но ничего не смог найти.
  4. Возможно, какая-нибудь другая библиотека, например JavaAssist или ASM, лучше подойдет для этого случая?

Ответы [ 2 ]

0 голосов
/ 06 апреля 2020

С AgentBuilder вы должны зарегистрировать Listener, чтобы увидеть, происходят ли ошибки во время ретрансформации. Вам, вероятно, следует установить .disableClassFormatChanges(), поскольку средняя JVM не поддерживает добавление методов или полей в класс, который уже определен.

Добавление поля или метода невозможно, как сегодня, только код Эволюция VM поддерживает его на сегодняшний день , и вряд ли эта функция когда-нибудь появится в OpenJDK.

0 голосов
/ 05 апреля 2020

Поскольку ваше требование заключается в создании инфраструктуры, с помощью которой вы можете собирать метрики приложений. Во-первых, есть такие инструменты, как VisualVM , которые помогают вам получать метрики из запущенного java приложения и видеть его идеи. Это будет полностью внешним по отношению к вашему приложению и не потребует никаких изменений кода.

Если вам нужен больший контроль над метриками, вы можете на борту Spring Boot Admin , и это будет сделано в режиме реального времени для вас без каких-либо изменений кода. Есть множество функций, присутствующих в Spring Boot Admin.

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