我希望有人解释这个项目,因为我可能会弄错:
我正在阅读Java Agent Instrumentation,它说代理可以在VM启动后启动。因此,如果我想动态替换某些类(没有简化应用程序)这就是我要使用agent-main的方法吗?或者我需要在这里做些什么吗?
我知道人们可能会问“你在谈论JRebel” - 不是因为我想做一些简单的事情而且JRebel是一种矫枉过正。
工具文档 - Java docs for Instrumentation
我理解所有的检测覆盖,但我有点困惑,我可以在应用程序启动后用-agent
参数挂钩此代理。
答案 0 :(得分:3)
首先,您的代理类需要指定一个agentmain
方法,如:
public class MyAgent {
public static void agentmain(final String args, final Instrumentation inst) {
try {
System.out.println("Agent loaded.");
} catch (Exception e) {
// Catch and handle every exception as they would
// otherwise be ignored in an agentmain method
e.printStackTrace();
}
}
}
编译它并将其打包在 jar -file中。如果您选择 jar -variant,则必须在其清单 -file中指定代理类密钥( MANIFEST.MF < / em>的)。它指向实现agentmain
方法的类。它可能看起来像:
Manifest-Version: 1.0
Agent-Class: package1.package2.MyAgent
如果它位于这些包中,作为示例。
之后,您可以通过VirtualMachine#loadAgent
方法(documentation)加载代理。请注意,这些类使用的机制是Java的 Attach库的一部分。他们决定,因为大多数用户不需要它,不能直接将它添加到系统路径,但你可以添加它。它位于
pathToYourJDKInstallation\jre\bin\attach.dll
它需要位于系统属性 java.library.path
所指向的位置。例如,您可以将其复制到.../Windows/System32
文件夹或调整属性或类似内容。
例如,如果您想在另一个当前正在运行的 jar 中注入 agent-jar ,您可以使用如下方法:
public void injectJarIntoJar(final String processIdOfTargetJar,
final String pathToAgentJar, final String[] argumentsToPass) {
try {
final VirtualMachine vm = VirtualMachine.attach(processIdOfTargetJar);
vm.loadAgent(pathToAgentJar, argumentsToPass.toString());
vm.detach();
} catch (AttachNotSupportedException | AgentLoadException
| AgentInitializationException | IOException e) {
System.err.println("Unable to inject jar into target jar.");
}
}
使用相同的技术,您可以将dll-libraries(如果它们通过本机代理程序接口实现相应的代理程序方法)注入jar中。
实际上,如果这对你有所帮助,我前段时间已经为这种东西写了一些小型库。请参阅Mem-Eater-Bug,相应的类为Injector.java,整个项目的Wiki小。
它有一个示例,展示了如何使用该技术来操作编写为Java应用程序的 SpaceInvaders 游戏。
答案 1 :(得分:2)
所以显然你想在运行时重新加载类。这样您的项目可以在不重新启动的情况下对代码的更改做出反应。
要实现这一目标,您需要准备项目并编写一个非常干净的架构,它涉及使用接口,工厂模式,代理模式以及检查更新的例程,然后销毁并重建所有当前对象。
不幸的是,这可能不是一项容易的任务,但它是可行的,具体取决于项目的大小以及应对变化做出动态反应的代码量。
我发现this article有帮助,让我解释一下它的工作原理。您可以使用ClassLoader.loadClass(...)
轻松加载类,也可以使用它来重新加载类,非常简单。但是,在您编译代码类时,已经存在某种硬连线。因此,您的旧代码将继续创建旧类的实例,尽管您已重新加载该类。
这就是为什么我们需要某种允许用新类交换旧类的架构的原因。此外很明显,旧类的当前实例无法自动转移到新版本,因为一切都可能已更改。因此,您还需要一个自定义方法来收集和重建这些实例。
本文中描述的方法首先使用Interface
而不是实际的类。这允许在不破坏使用接口的代码的情况下轻松地交换该接口后面的类。
然后你需要一个工厂,你要求Interface
的实例。工厂现在可以检查基础类文件是否已更改,如果是,则重新加载它并获得对新类版本的引用。它现在总是可以创建一个使用最新类的接口实例。
如果代码库已经更改,那么工厂也可以收集所有创建的实例,以便以后进行交换。但是工厂应该使用WeakReference
(documentation)引用它们,否则你的内存泄漏很大,因为垃圾收集器无法删除实例,因为工厂保存了对它们的引用。
好的,现在我们能够始终获得Interface
的最新实现。但是,我们如何轻松地交换现有实例。答案是使用代理模式(explanation)。
很简单,你有一个代理类,这是你正在使用的实际对象。它具有Interface
的所有方法,并且在调用方法时它只是转发到真实类。
您的工厂,因为它有一个使用WeakReference
的所有当前实例的列表,现在可以迭代代理列表并使用新的最新版本的对象交换它们的真实类。
项目周围使用的现有代理现在将自动使用新的真实版本,因为代理本身没有更改,只有它对真实目标的内部引用已更改。
现在有一些示例代码可以为您提供一个粗略的想法。
要监控的对象的界面:
public interface IExample {
void example();
}
您要重建的真实班级:
public class RealExample implements IExample {
@Override
public void example() {
System.out.println("Hi there.");
}
}
您实际使用的代理类:
public class ProxyExample implements IExample {
private IExample mTarget;
public ProxyExample(final IExample target) {
this.mTarget = target;
}
@Override
public void example() {
// Forward to the real implementation
this.mRealExample.example();
}
public void exchangeTarget(final IExample target) {
this.mTarget = target;
}
}
工厂您将主要使用:
public class ExampleFactory {
private static final String CLASS_NAME_TO_MONITOR = "somePackage.RealExample";
private final List<WeakReference<ProxyExample>> mInstances;
private final URLClassLoader mClassLoader;
public ExampleFactory() {
mInstances = new LinkedList<>();
// Classloader that will always load the up-to-date version of the class to monitor
mClassLoader = new URLClassLoader(new URL[] {getClassPath()}) {
public Class loadClass(final String name) {
if (CLASS_NAME_TO_MONITOR.equals(name)) {
return findClass(name);
}
return super.loadClass(name);
}
};
}
private IExample createRealInstance() {
return (IExample) this.mClassLoader.loadClass(CLASS_NAME_TO_MONITOR).newInstance();
}
public IExample createInstance() {
// Create an up-to-date instance
final IExample instance = createRealInstance();
// Create a proxy around it
final ProxyExample proxy = new ProxyExample(instance);
// Add the proxy to the monitor
this.mInstances.add(proxy);
return proxy;
}
public void updateAllInstances() {
// Iterate the proxies and update their references
// Use a ListIterator to easily remove instances that have been cleared
final ListIterator<WeakReference<ProxyExample>> instanceIter =
this.mInstances.listIterator();
while (instanceIter.hasNext()) {
final WeakReference<ProxyExample> reference = instanceIter.next();
final ProxyExample proxy = reference.get();
// Remove the instance if it was already cleared,
// for example by the garbage collector
if (proxy == null) {
instanceIter.remove();
continue;
}
// Create an up-to-date instance for exchange
final IExample instance = createRealInstance();
// Update the target of the proxy instance
proxy.exchangeTarget(instance);
}
}
}
最后如何使用:
public static void main(final String[] args) {
final ExampleFactory factory = new ExampleFactory();
// Get some instances using the factory
final IExample example1 = factory.createInstance();
final IExample example2 = factory.createInstance();
// Prints "Hi there."
example1.example();
// Update all instances
factory.updateAllInstances();
// Prints whatever the class now contains
example1.example();
}
答案 2 :(得分:0)
在运行时附加代理需要使用<input type="checkbox" class="toggleone" name="attendance[<?php echo $i; ?>]" id="attendance_<?php echo $i; ?>" data-toggle="toggle" data-on="Yes" data-off="No">
中包含的attach API,直到Java 8,并且从Java 9开始包含在自己的模块中。tools.jar
的位置和其类的名称取决于系统(操作系统,版本,供应商),从Java 9开始,它根本不存在,但必须通过其模块解析。
如果您正在寻找一种简便的方法来访问此功能,请试用Byte Buddy,其中包含子项目tools.jar
。按照习惯创建Java代理,但添加byte-buddy-agent
条目,将Agent-Main
放在清单中。另外,将输入法命名为Pre-Main
,而不是agentmain
。
使用premain
,您可以编写程序:
byte-buddy-agent
你已经完成了。