Добавление кода в пакетный метод частной библиотеки

У меня есть класс библиотеки с приватным методом пакета. Непосредственное переопределение этого метода подклассом не является вариантом. Есть ли какой-либо способ, независимо от того, насколько уродливый, выполнять собственный код, когда этот закрытый метод пакета вызывается из библиотеки, например. используя AspectJ?

Вот упрощенный пример класса (фактически packagePrivateMethod() не вызывается напрямую, а из собственного кода):

public LibClass {

  public LibClass() {
    ...
    packagePrivateMethod();
    ...
  }

  void packagePrivateMethod() {
    // <-- here I want to execute additional code
    ...
  }
}

Ответ 1

Вы можете использовать довольно тяжелый подход.

  • Напишите небольшой Java-агент SO сообщение об этой теме.
  • Используйте предоставленный Инструментальный интерфейс, чтобы перехватить загрузку класса.
  • Используйте библиотеку модификации кода байта (например, ASM или Java Assist (только Java 6!)), Чтобы задействовать байтовый код (например, чтобы заменить вызов метода тем, что вы действительно хотите сделать.

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

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

Если вы не хотите/не можете статически заменить байтовый код класса, вам придется выполнить модификацию байт-кода во время выполнения. Для использования Java-агента это хорошая и прочная идея.

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

Итак, вот код примера к вашей проблеме, разрешенный с помощью Java 6 и JavaAssist. Если вы хотите пойти по этому пути и использовать что-то более новое, как Java 7, вам просто нужно заменить манипуляцию с байтовым кодом на ASM. Это немного менее читаемо, но также не совсем научная ракета.

Основной класс:

package com.aop.example;

public class Main {

  public static void main(String[] args) {
    System.out.println("Main starts!");
    LibClass libClass = new LibClass();
    System.out.println("Main finished!");
  }
}

Ваш LibClass:

package com.aop.example;

public class LibClass {

  public LibClass() {
    packagePrivateMethod();
  }

  void packagePrivateMethod() {
    // <-- here I want to execute additional code
    System.out.println("In packagePrivateMethod");
  }
}

Агент:

package com.aop.agent;

import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.LoaderClassPath;
import javassist.NotFoundException;

public class Agent {

  public static void premain(String agentArgs, Instrumentation instr) {
    System.out.println("Agent starts!");
    instr.addTransformer(new ClassFileTransformer() {

      @Override
      public byte[] transform(ClassLoader classLoader, String className, Class<?> arg2, ProtectionDomain arg3,
          byte[] bytes)
          throws IllegalClassFormatException {
        System.out.println("Before loading class " + className);

        final String TARGET_CLASS = "com/aop/example/LibClass";

        if (!className.equals(TARGET_CLASS)) {
          return null;
        }

        LoaderClassPath path = new LoaderClassPath(classLoader);
        ClassPool pool = new ClassPool();
        pool.appendSystemPath();
        pool.appendClassPath(path);

        try {
          CtClass targetClass = pool.get(TARGET_CLASS.replace('/', '.'));
          System.out.println("Enhancing class " + targetClass.getName());
          CtMethod[] methods = targetClass.getDeclaredMethods();
          for (CtMethod method : methods) {
            if (!method.getName().contains("packagePrivateMethod")) {
              continue;
            }
            System.out.println("Enhancing method " + method.getSignature());
            String myMethodInvocation = "com.aop.agent.Agent.myMethodInvocation();";
            method.insertBefore(myMethodInvocation);
          }
          System.out.println("Enhanced bytecode");

          return targetClass.toBytecode();
        }
        catch (CannotCompileException e) {
          e.printStackTrace();
          throw new RuntimeException(e);
        }
        catch (IOException e) {
          e.printStackTrace();
          throw new RuntimeException(e);
        }
        catch (NotFoundException e) {
          e.printStackTrace();
          throw new RuntimeException(e);
        }
      }

    });
  }

  public static void myMethodInvocation() {
    System.out.println("<<<My injected code>>>!");
  }
}

Команда для запуска примера (вы должны поместить агента в банку с манифестом, имеющим атрибут Premain-Class: com.aop.agent.Agent:

%JAVA_HOME%\bin\java -cp .;..\javassist-3.12.1.GA.jar -javaagent:..\..\agent.jar com.aop.example.Main

Результат этого примера запускает следующую команду:

Agent starts!
Before loading class com/aop/example/Main
Main starts!
Before loading class com/aop/example/LibClass
Enhancing class com.aop.example.LibClass
Enhancing method ()V
Enhanced bytecode
<<<My injected code>>>!
In packagePrivateMethod
Main finished!
Before loading class java/lang/Shutdown
Before loading class java/lang/Shutdown$Lock

Ответ 2

Вы можете Mockito или подобную библиотеку mock для издевательства частного метода пакета. Пример:

// declared in a package
public class Foo {
    String foo(){
        return "hey!";
    }
}

@Test
public void testFoo() throws Exception {
    Foo foo = Mockito.spy(new Foo());

    Assert.assertEquals("hey!", foo.foo());

    Mockito.when(foo.foo()).thenReturn("bar!");

    Assert.assertEquals("bar!", foo.foo());

}

Ответ 3

Можете ли вы добавить Spring в свой проект? Возможно, можно будет использовать ProxyFactory - см. еще одно сообщение SO

Используя ProxyFactory, вы можете добавить совет для экземпляра класса и делегировать выполнение метода другому классу (который делает packagePrivateMethod() и/или заменяет его кодом, который вы хотите).

Поскольку библиотека не является spring -управляемой, вам может потребоваться использовать время загрузки во времени с помощью spring: ltw xml и примеры

Ответ 4

используйте шаблон декоратора. Он специально разработан для этой ситуации. Если вам нужны более подробные сведения, а затем выполните ping me back else, проверьте this

Или вы также можете использовать рефлексы или механизм манипулирования байтовым кодом для динамического создания своего типа во время выполнения.

Ответ 5

Другая идея: создать новый класс с тем же именем в одном пакете.

Предположим, вы хотите заменить LibraryClass в следующем проекте:

Project structure:
  - library.jar (contains com.example.LibraryClass)
  - src
    - com
      - mycompany
        - MyClass.java

Просто создайте пакет и файл с тем же именем.

Project structure:
  - library.jar (contains com.example.LibraryClass)
  - src
    - com
      - mycompany
        - MyClass.java
      - example
        - LibraryClass.java  <- create this package and file

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

Если у вас нет исходного кода для LibraryClass, просто скопируйте декомпилированный код и внесите изменения.

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