确定JVM的命令行根类

时间:2011-04-11 21:31:53

标签: java

当子类从超类继承 main()时,是否可以确定在命令行上调用的实际类?例如,请考虑以下两个类别,其中主要 A 实施,并由 B 继承:

public class A {

    public static void main(String[] args) throws Exception {
        // Replace with <some magic here> to determine the class 
        //    invoked on the command-line
        final Class<? extends A> c = A.class;
        System.out.println("Invoked class: " + c.getName());

        final A instance = c.newInstance();
        // Do something with instance here...
    }
}

public class B extends A {
}

我们可以成功调用 B (即 B 确实'继承' main - 至少在任何意义上都可以继承静态方法) ,但我还没有找到一种方法来确定用户调用的实际类:

$ java -cp . A
Invoked class: A

$ java -cp . B
Invoked class: A

我最接近的是要求子类实现 main()并在超类中调用辅助方法,然后读取线程堆栈以确定调用类:

public class AByStack {

    public static void run(String[] args) throws Exception {
        // Read the thread stack to find the calling class
        final Class<? extends AByStack> c = (Class<? extends AByStack>)
            Class.forName(Thread.currentThread().getStackTrace()[2].getClassName());
        System.out.println("Invoked class: " + c.getName());

        final AByStack instance = c.newInstance();
        // Do something with instance here...
    }

    public static void main(String[] args) throws Exception {
        run(args);
    }
}

public class BByStack extends AByStack {

    public static void main(String[] args) throws Exception {
        // Call the master 'run' method
        run(args);
    }
}

此方法有效:

$ java -cp . AByStack
Invoked class: AByStack

$ java -cp . BByStack
Invoked class: BByStack

但是我真的想消除子类实现 main()的要求(是的,叫我挑剔......)。我不介意它是否需要一些丑陋的代码,因为它将被实现一次并埋没在基类中,并且我最感兴趣的是Sun / Oracle VM,所以我愿意考虑使用私有sun。 misc class或类似的东西。

但我确实想避免平台依赖。例如,在Linux上,我们可以查看 / proc / self / cmdline ,但这当然不能移植到Windows(我不确定Mac OS - 我的Mac没有我现在要测试这个技巧)。我认为JNI和JVMTI出于同样的原因出局。我可能错了JVMTI,但在我看来它需要一个C包装器。如果没有,也许我们可以以某种方式使用该界面。

多年前在http://www.coderanch.com/t/375326/java/java/Getting-command-line-class问了这个问题。最好的答案是每个子类中都需要一个静态初始化程序块 - 对于我所演示的调用 run()解决方案的子类作者的不同但类似的要求。但我没有看到最近的讨论;我希望当前的VM可能允许访问在讨论时无法获得的信息。

3 个答案:

答案 0 :(得分:0)

大概你想要这个,以便可以使用不同的名称调用某些工具,但根据调用的名称,其行为大致相同?还是一些类似的魔法?

在这种情况下,你可以简单地在所有子类中使用一个实际的main()方法,并委托给一个方法,该方法也采用被调用类的名称:

public class Super {
    protected void doMain(String invokee, String... args) {
        System.out.println("I was invoked as: " + invokee);
    }
}

public class ToolA {
    public static void main(String... args) {
        new Super().doMain("ToolA", args);  // or ToolA.class.getName() to be refactor-proof
    }
}

答案 1 :(得分:0)

我想我只是修改run()方法的签名,而不是使用调用堆栈,在每个子类中实现main():

public class BByStack extends AByStack {

    public static void main(String[] args) throws Exception {
        // Call the master 'run' method
        run(BByStack.class, args);
    }
}

如果您正在使用默认构造函数进行实例化,我甚至只需传入

new BByStack()

我不知道有什么方法可以做你所要求的;该方法是静态的,因此您无法获得this.getClass()等的句柄;如果未在子类上定义该方法,则也无法从堆栈中获取该信息。

答案 2 :(得分:0)

由于我浪费了相当多的时间进行调查而不是问题,我会在这里发布我的结论。

首先,试图重申问题的基本原理:

我将参数处理,I / O和其他共享任务抽象为抽象超类,我希望其他人可以扩展。在执行参数解析和共享设置之后,超类中的静态方法实例化子类的实例并调用其 run()方法。

鼓励子类的作者实现 public static void main(String [])并调用超类的主入口点。但是,与所有子类实现 run()的要求不同,我们不能在编译时静态强制执行该要求(因为Java没有抽象静态方法的概念)。 / p>

所以我试图在超类中实现一个 main(String [])方法,该方法可以确定在命令行上请求的子类的名称并实例化相应的类。

我找到了两种特定于Sun / Oracle JVM的方法。

第一个使用内部sun.jvmstat类:

import java.lang.management.ManagementFactory;
import sun.jvmstat.monitor.MonitoredVmUtil;
import sun.jvmstat.monitor.VmIdentifier;
import sun.jvmstat.perfdata.monitor.protocol.local.LocalMonitoredVm;

...

public static String jvmstatMainClass() {
    // Determine the VMID (on most platforms, this will be the PID)
    final String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];

    // Connect to the virtual machine by VMID
    final VmIdentifier vmId = new VmIdentifier(pid);
    final LocalMonitoredVm lmVm = new LocalMonitoredVm(vmId, 1000);

    // Find the requested main-class
    String mainClass = MonitoredVmUtil.mainClass(lmVm, true);

    // And detach from the VM
    lmVm.detach();

    return mainClass;
}

第二个使用Sun的 jps 实用程序:

import java.io.BufferedReader;
import java.io.InputStreamReader;

...

public static String jpsMainClass() {
    // Determine the VMID (on most platforms, this will be the PID)
    final String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];

    // Execute the 'jps' utility
    final Process jps = Runtime.getRuntime().exec(new String[] { "jps", "-l" });
    final BufferedReader br = new BufferedReader(new InputStreamReader(jps.getInputStream()));

    // Parse the output of jps to find the current VM by PID
    for (String line = br.readLine(); line != null; line = br.readLine()) {
        final String[] split = line.split(" ");
        if (pid.equals(split[0])) {
                return split[1];
            }
        }
    }
    return null;
}

希望我浪费的时间对其他人有帮助。