是否可以制作 Java 可执行文件?
Is it possible to make a Java executable?
明确地说,我所说的可执行文件并不是指为处理器准备好的文字字节。例如,一个被解释但不可执行的 bash 脚本,在将 shebang 添加到顶部指定脚本应该 运行 by /bin/bash
或 /bin/sh
时变得可执行或者任何程序将解释它。
我想知道是否可以使用 Java,它在技术上不是脚本语言,但绝对不可执行。似乎 Java 很难,因为用户实际上没有机会向编译文件添加 shebang,并且编译的 java 不能来自标准输入。
你当然可以创建一个文件:
#!/any/executable/program args
...input goes here...
你可以用 Java
#!/path/bin/java mainclass
...this is System.in...
没有。不可能在任何脚本上放一个 she bang 并且它会执行。 Bash 依赖于带有 shebang 的文件会忽略以 #
开头的行这一事实。因此,任何可以忽略带有 shebang 的第一行的脚本语言或字节代码都可以工作。
如果您的语言不支持 # 作为注释或忽略第一行,则需要通过另一种忽略它的脚本语言。
话虽如此,您可以拥有 bash 具有可调用的内联二进制 blob 的脚本。游戏安装程序会这样做。
与其编写大量代码让 Java 以源代码形式执行,您有几个选择:
使用 Scala!您知道 Scala 是基于 Java 构建的吗?它有一个解释器和编译器。您可以 运行 一个脚本,一个 shell 或编译并 运行 它。 Scala 和 Java 无缝协作。两者都在 JVM 上编译为相同的字节码和 运行。是的,这门语言会让人觉得很奇怪,因为 Scala 就像是 Java、R 和 Python 的混合体,但大部分核心语言没有变化,所有 Java 包都可用。试试 Scala。如果你在 Linux,你不妨也看看 Spark,即使是一台机器。
如果你坚持只使用 Java,你可以创建一个混合程序来做两件事(我以前做过):编译代码和 运行 它。可执行文件甚至 bash 脚本都可以执行获取源文件并使它们可执行的工作。如果你想做一个 Java shell,那么你需要做一个动态的 运行time compiler/loader,但你只需要使用已经 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;
}
}
两年半后,我偶然发现了一个比 2016 年给出的答案更完整的答案。Java 二进制文件可以嵌入到可执行文件中,这与 John Hascall 的答案相反。 This article explains how this can be done in linux and unix like systems by adding a binary payload to a shell script.
我将提供该过程的简短摘要。
给定一个名为any_java_executable.jar
的可执行jar
给定 你想创建一个名为 my_executable
的可执行文件
给定一个名为basis.sh
的脚本文件,其内容如下
#!/bin/sh
MYSELF=`which "[=10=]" 2>/dev/null`
[ $? -gt 0 -a -f "[=10=]" ] && MYSELF="./[=10=]"
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 工具在任何地方使用。
从 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 被标记为已弃用。
明确地说,我所说的可执行文件并不是指为处理器准备好的文字字节。例如,一个被解释但不可执行的 bash 脚本,在将 shebang 添加到顶部指定脚本应该 运行 by /bin/bash
或 /bin/sh
时变得可执行或者任何程序将解释它。
我想知道是否可以使用 Java,它在技术上不是脚本语言,但绝对不可执行。似乎 Java 很难,因为用户实际上没有机会向编译文件添加 shebang,并且编译的 java 不能来自标准输入。
你当然可以创建一个文件:
#!/any/executable/program args
...input goes here...
你可以用 Java
#!/path/bin/java mainclass
...this is System.in...
没有。不可能在任何脚本上放一个 she bang 并且它会执行。 Bash 依赖于带有 shebang 的文件会忽略以 #
开头的行这一事实。因此,任何可以忽略带有 shebang 的第一行的脚本语言或字节代码都可以工作。
如果您的语言不支持 # 作为注释或忽略第一行,则需要通过另一种忽略它的脚本语言。
话虽如此,您可以拥有 bash 具有可调用的内联二进制 blob 的脚本。游戏安装程序会这样做。
与其编写大量代码让 Java 以源代码形式执行,您有几个选择:
使用 Scala!您知道 Scala 是基于 Java 构建的吗?它有一个解释器和编译器。您可以 运行 一个脚本,一个 shell 或编译并 运行 它。 Scala 和 Java 无缝协作。两者都在 JVM 上编译为相同的字节码和 运行。是的,这门语言会让人觉得很奇怪,因为 Scala 就像是 Java、R 和 Python 的混合体,但大部分核心语言没有变化,所有 Java 包都可用。试试 Scala。如果你在 Linux,你不妨也看看 Spark,即使是一台机器。
如果你坚持只使用 Java,你可以创建一个混合程序来做两件事(我以前做过):编译代码和 运行 它。可执行文件甚至 bash 脚本都可以执行获取源文件并使它们可执行的工作。如果你想做一个 Java shell,那么你需要做一个动态的 运行time compiler/loader,但你只需要使用已经 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;
}
}
两年半后,我偶然发现了一个比 2016 年给出的答案更完整的答案。Java 二进制文件可以嵌入到可执行文件中,这与 John Hascall 的答案相反。 This article explains how this can be done in linux and unix like systems by adding a binary payload to a shell script.
我将提供该过程的简短摘要。
给定一个名为any_java_executable.jar
的可执行jar
给定 你想创建一个名为 my_executable
的可执行文件
给定一个名为basis.sh
的脚本文件,其内容如下
#!/bin/sh
MYSELF=`which "[=10=]" 2>/dev/null`
[ $? -gt 0 -a -f "[=10=]" ] && MYSELF="./[=10=]"
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 工具在任何地方使用。
从 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 被标记为已弃用。