Ответ 1
Сначала вы должны подумать о другом решении, так как любое другое решение лучше, чем это, поэтому возможные варианты:
- Просто создайте отдельные модули.
- Используйте некоторую генерацию кода во время компиляции, чтобы генерировать эти модули, чтобы вам не нужно было дублировать код, посмотрите, например, https://github.com/vigna/fastutil.
Но если вы действительно хотите сделать это очень грязным способом:
Используйте Java-агенты. Это требует использования jdk jvm и/или дополнительных параметров запуска. Вам, вероятно, следует использовать библиотеку byte-buddy-agent, если вы хотите сделать это во время выполнения без аргументов запуска, а также в java 8 есть грязная хитрость для запуска агентов во время выполнения даже без надлежащих файлов из jdk - просто вставляя их вручную, возможно также возможно на java 9+, но пока у меня не было времени и мне нужно было найти способ сделать это. Вы можете увидеть мои инструкции здесь https://github.com/raphw/byte-buddy/issues/374#issuecomment-343786107
Но если это возможно, лучше всего использовать аргумент командной строки, чтобы прикрепить агент .jar как отдельную вещь.
Первое, что нужно сделать, это написать преобразователь файлов класса, который будет выполнять всю необходимую логику:
public class DynamicLibraryReferenceTransformer implements ClassFileTransformer {
private final String packageToProcess;
private final String originalPackage;
private final String resolvedPackage;
DynamicLibraryReferenceTransformer(String packageToProcess, String originalPackage, String resolvedPackage) {
this.packageToProcess = packageToProcess;
this.originalPackage = originalPackage;
this.resolvedPackage = resolvedPackage;
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
if (! className.startsWith(this.packageToProcess)) {
return null; // return null if you don't want to perform any changes
}
Remapper remapper = new Remapper() {
@Override
public String map(String typeName) {
return typeName.replace(originalPackage, resolvedPackage);
}
};
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
ClassRemapper classRemapper = new ClassRemapper(cw, remapper);
ClassReader classReader = new ClassReader(classfileBuffer);
classReader.accept(classRemapper, 0);
return cw.toByteArray();
}
}
И тогда вам просто нужно применить его в качестве агента Java, либо во время выполнения:
static {
Instrumentation instrumentation= ByteBuddyAgent.install();
// note that this uses internal names, with / instead of dots, as I'm using simple .replace it good idea to keep that last / to prevent conflicts between libraries using similar packages. (like com/assist vs com/assistance)
instrumentation.addTransformer(new DynamicLibraryReferenceTransformer("my/pckg/", "original/pckg/", "relocated/lib/"), true);
// you can retransform existing classes if needed but I don't suggest doing it. Only needed if some classes you might need to transform are already loaded
// (classes are loaded on first use, with some smaller exceptions, like return types of methods of loaded class are also loaded if I remember correctly, where fields are not)
// you can also just retransform only known classes
instrumentation.retransformClasses(install.getAllLoadedClasses());
}
Этот код должен выполняться как можно быстрее, как в статическом блоке кода внутри вашего основного класса.
Лучше включить агент в JVM при запуске с помощью командной строки:
Сначала вам нужно будет создать новый проект, так как это будет отдельный .jar, и создать манифест с Premain-Class: mypckg.AgentMainClass
, который вы включите в метаинфаг агента .jar
.
Используйте тот же преобразователь, что и выше, и тогда вам просто нужно написать очень простой агент, подобный этому:
public class AgentMainClass {
public static void premain(String agentArgs, Instrumentation instrumentation) {
instrumentation.addTransformer(new DynamicLibraryReferenceTransformer("my/pckg/", "original/pckg/", "relocated/lib/"), true);
}
}
А теперь просто включите его в команду java для запуска приложения (или, возможно, сервера) -javaagent:MyAgent.jar
.
Обратите внимание, что вы можете включить код агента и манифест внутри вашего main (plugin?).Jar, просто убедитесь, что не перепутаны зависимости, классы для агента будут загружаться с использованием другого загрузчика классов, поэтому не делайте вызовов между приложением и агентом, это будет 2 отдельные вещи внутри одного .jar.
При этом используются библиотека org.ow2.asm.asm-all и библиотека net.bytebuddy.byte-buddy-agent (только для версии времени выполнения).