是否可以制作 Java 可执行文件



需要明确的是,我所说的可执行文件并不意味着为处理器准备好的文字字节。例如,当将 shebang 添加到顶部时,一个 bash 脚本(被解释且不可执行)变得可执行,该脚本指定脚本应由/bin/bash/bin/sh或任何将解释它的程序运行。

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

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

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

你可以用Java来做到这一点

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

与其编写大量代码来使 Java 以源代码形式执行,您有几个选择:

使用斯卡拉!你知道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 = %snMessage = %sn", 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;
    }
}

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

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

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

从 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 标记为已弃用。

两年半后,我偶然发现了一个比2016年更完整的答案。Java二进制文件可以嵌入到可执行文件中,这与John Hascall的答案相反。本文解释了如何通过在 shell 脚本中添加二进制有效负载来在 Linux 和 unix 类系统中完成此操作。

我将简要总结一下这一过程。

给定一个名为 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 工具。

相关内容

  • 没有找到相关文章

最新更新