【问题标题】:Is it possible to make a Java executable?是否可以使 Java 可执行文件?
【发布时间】:2016-02-25 07:27:13
【问题描述】:

需要明确的是,可执行文件并不是指为处理器准备好的文字字节。例如,解释但不可执行的 bash 脚本在添加到顶部的 shebang 时变为可执行,指定脚本应由 /bin/bash/bin/sh 或任何将解释它的程序运行。

我想知道是否可以使用 Java,它在技术上不是一种脚本语言,但绝对不可执行。看起来Java会很难,因为用户实际上并没有机会在编译后的文件中添加shebang,而且编译后的java不能来自stdin。

【问题讨论】:

    标签: java executable bin shebang interpreted-language


    【解决方案1】:

    你当然可以创建一个文件:

    #!/any/executable/program args
    ...input goes here...
    

    你可以用 Java 做到这一点

    #!/path/bin/java mainclass
    ...this is System.in...
    

    【讨论】:

    • 啊,所以对于 Java,.class 文件必须存储在其他地方并由可执行文件引用?
    • 是的,这是正确的,您必须在运行之前确保类路径正确。所以平时用sh脚本比较实用。
    • 这很有趣。我不知道你也可以在 shebang 之后传递参数。我想这就是我自学的收获哈哈。答案已获批准!
    • 参数的数量和大小可能会受到限制。通常,您会在 #!/bin/sh -x 或诸如此类的地方看到 shell 标志。
    • 嘿,很抱歉这么久才把这个胜利从你身边夺走,但我最近偶然发现了一个更令人满意的答案,我决定分享它。事实证明,java 二进制文件不需要 需要存储在其他地方并被引用。请随时阅读我的​​帖子,如果您对此有任何想法,请告诉我。
    【解决方案2】:

    您不必编写大量代码以使 Java 以源代码形式执行,您有以下几种选择:

    使用 Scala!你知道 Scala 是基于 Java 构建的吗?它有一个解释器和编译器。您可以运行脚本、shell 或编译并运行它。 Scala 和 Java 无缝协作。两者都编译为相同的字节码并在 JVM 上运行。是的,这种语言会让人感觉很奇怪,因为 Scala 就像是 Java、R 和 Python 之间的交叉,但大多数核心语言都没有改变,所有 Java 包都可用。试试 Scala。如果你在 Linux 上,你不妨也看看 Spark,即使是一台机器。

    如果你坚持只使用 Java,你可以创建一个混合程序来做两件事(我以前做过):编译代码并运行它。可执行文件甚至 bash 脚本可以完成获取源文件并使它们可执行的工作。如果您正在寻找制作 Java shell,那么您需要制作一个动态运行时编译器/加载器,但您只需要使用 Java/Oracle 已经提供给我们的东西。想象一下从我放置打印语句的文件中插入 Java 语法。只要它可以编译,你就可以在那里拥有任何你想要的东西。看这个例子:

    package util.injection;
    
    import java.io.File;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.io.Writer;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.net.URLClassLoader;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Locale;
    import javax.tools.Diagnostic;
    import javax.tools.DiagnosticCollector;
    import javax.tools.JavaCompiler;
    import javax.tools.JavaFileObject;
    import javax.tools.StandardJavaFileManager;
    import javax.tools.ToolProvider;
    
    public class Compiler {
    
        static final long t0 = System.currentTimeMillis();
    
        public static void main(String[] args) {
            StringBuilder sb = new StringBuilder(64);
            String packageName = "util";
            String className = "HelloWorld";
            sb.append("package util;\n");
            sb.append("public class HelloWorld extends " + Function.class.getName() + " {\n");
            sb.append("    public void test() {\n");
            sb.append("        System.out.println(\"Hello from dynamic function!\");\n");
            sb.append("    }\n");
            sb.append("}\n");
            String code = sb.toString();
    
            String jarLibraryFile = "target/myprojectname.jar";
    
            Function dynFunction = code2class(packageName, className, code, jarLibraryFile);
            dynFunction.test();
        }
    
        public static Function code2class(String packageName, String className, String code, String jarLibraryFile) {
            String wholeClassName = packageName.replace("/", ".") + "." + className;
            String fileName = wholeClassName.replace(".", "/") + ".java";//"testcompile/HelloWorld.java";
            File javaCodeFile = new File(fileName);
            string2file(javaCodeFile, code);
            Function dynFunction = null;
            try {
    
                boolean success = compile(jarLibraryFile, javaCodeFile);
    
                /**
                 * Load and execute
                 * ************************************************************************************************
                 */
                System.out.println("Running... " + (System.currentTimeMillis() - t0) + " ms");
                Object obj = load(wholeClassName);
                // Santity check
                if (obj instanceof Function) {
                    dynFunction = (Function) obj;
                    // Run it 
                    //Edit: call dynFunction.test(); to see something
                }
                System.out.println("Finished... " + (System.currentTimeMillis() - t0) + " ms");
            } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException exp) {
                exp.printStackTrace();
            }
            return dynFunction;
        }
    
        public static boolean compile(String jarLibraryFile, File javaCodeFile) throws IOException {
            /**
             * Compilation Requirements
             * ********************************************************************************************
             */
            System.out.println("Compiling... " + (System.currentTimeMillis() - t0) + " ms");
            DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
    
            // This sets up the class path that the compiler will use.
            // I've added the .jar file that contains the DoStuff interface within in it...
            List<String> optionList = new ArrayList<>(2);
            optionList.add("-classpath");
            optionList.add(System.getProperty("java.class.path") + ";" + jarLibraryFile);
    
            Iterable<? extends JavaFileObject> compilationUnit
                    = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(javaCodeFile));
            JavaCompiler.CompilationTask task = compiler.getTask(
                    null,
                    fileManager,
                    diagnostics,
                    optionList,
                    null,
                    compilationUnit);
            fileManager.close();
    
            /**
             * *******************************************************************************************
             * Compilation Requirements *
             */
            if (task.call()) {
                return true;
                /**
                 * ***********************************************************************************************
                 * Load and execute *
                 */
            } else {
                for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
                    System.out.format("Error on line %d in %s%n",
                            diagnostic.getLineNumber(),
                            diagnostic.getSource().toUri());
                    System.out.printf("Code = %s\nMessage = %s\n", diagnostic.getCode(), diagnostic.getMessage(Locale.US));
    
                }
            }
            return false;
        }
    
        public static void string2file(File outputFile, String code) {
            if (outputFile.getParentFile().exists() || outputFile.getParentFile().mkdirs()) {
    
                try {
                    Writer writer = null;
                    try {
                        writer = new FileWriter(outputFile);
                        writer.write(code);
                        writer.flush();
                    } finally {
                        try {
                            writer.close();
                        } catch (Exception e) {
                        }
                    }
                } catch (IOException exp) {
                    exp.printStackTrace();
                }
            }
        }
    
        public static Object load(String wholeClassName) throws IllegalAccessException, InstantiationException, ClassNotFoundException, MalformedURLException {
            // Create a new custom class loader, pointing to the directory that contains the compiled
            // classes, this should point to the top of the package structure!
            URLClassLoader classLoader = new URLClassLoader(new URL[]{new File("./").toURI().toURL()});
            // Load the class from the classloader by name....
            Class<?> loadedClass = classLoader.loadClass(wholeClassName);
            // Create a new instance...
            Object obj = loadedClass.newInstance();
            return obj;
        }
    
    }
    

    ..

    package util.injection;
    
    public class Function {
    
        private static final long serialVersionUID = 7526472295622776147L;
    
        public void test() {
                    System.out.println("Hello from original Function!");
        }
    
        public int getID() {
            return -1;
        }
    
        public void apply(float[] img, int x, int y) {
    
        }
    
        public double dot(double[] x, double[] y) {
                return 0;
        }
    }
    

    【讨论】:

      【解决方案3】:

      没有。不可能在任何脚本上放一个 she bang 并且它会执行。 Bash 依赖于这样一个事实,即带有 shebang 的文件会忽略以 # 开头的行。因此,任何可以忽略带有 shebang 的第一行的脚本语言或字节码都可以工作。

      如果您的语言不支持 # 作为注释或忽略第一行,则需要通过另一种忽略它的脚本语言。

      话虽如此,您可以拥有可以调用的内联二进制 blob 的 bash 脚本。游戏安装程序会这样做。

      【讨论】:

      【解决方案4】:

      从JDK11开始可以直接用源码做:

      #!/usr/lib/jvm/jdk-11/bin/java --source 8
      
      public class Oneliner {
        public static void main(String[] args){
          System.out.println("ok");
        }
      }
      

      注意,如果文件扩展名不是.java,则--source 参数是必需的。值 6-11 受支持,但 6 被标记为已弃用。

      【讨论】:

        【解决方案5】:

        两年半后,我偶然发现了一个比 2016 年给出的更完整的答案。Java 二进制文件可以嵌入到可执行文件中,这与 John Hascall 的答案相反。 This article 解释说adding a binary payload to a shell script.

        我将简要介绍该过程。

        给定一个名为 any_java_executable.jar 的可执行 jar
        假设您想创建一个名为 my_executable 的可执行文件
        给定一个名为 basis.sh 的脚本文件,其内容如下

        #!/bin/sh
        MYSELF=`which "$0" 2>/dev/null`
        [ $? -gt 0 -a -f "$0" ] && MYSELF="./$0"
        java=java
        if test -n "$JAVA_HOME"; then
            java="$JAVA_HOME/bin/java"
        fi
        exec "$java" $java_args -jar $MYSELF "$@"
        exit 1
        

        可以通过运行以下两个命令来创建本机可执行文件。

        cat basis.sh any_java_executable.jar > my_executable;
        chmod +x my_executable;
        

        那么my_executable是一个本机可执行文件,能够运行java程序而不依赖于jar文件的位置。可以通过运行来执行

        ./my_executable [arg1 [arg2 [arg3...]]]
        

        如果放置在/usr/local/bin 中,则可以在任何地方用作 CLI 工具。

        【讨论】:

        • 这个“有效”是因为 Java 在其 jar 文件的开头忽略了脚本“垃圾”。但是,我找不到任何地方可以将这种行为记录为您可以依靠的东西。因此,除非它是一个记录在案的功能 - 可能会起作用,可能不会,可能会停止工作,可能会让猴子飞出你的鼻子。
        猜你喜欢
        • 1970-01-01
        • 2021-07-25
        • 2018-11-04
        • 1970-01-01
        • 1970-01-01
        • 2017-06-20
        • 1970-01-01
        • 1970-01-01
        • 2015-06-30
        相关资源
        最近更新 更多