Is it possible to make a Java executable?

流过昼夜 提交于 2019-12-10 14:57:35

问题


To be clear, by executable I do not mean literal bytes ready for the processor. For example a bash script, which is interpreted and not executable, becomes executable when a shebang is added to the top that specifies the script should be run by /bin/bash or /bin/sh or whatever program will be interpreting it.

I was wondering if it's possible to do with Java, which is not technically a scripting language but is definitely not executable. It seems like Java would be hard because the user doesn't actually have the opportunity to add a shebang to the compiled file, and the compiled java cannot come from stdin.


回答1:


You can certainly create a file:

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

You could do it with Java

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



回答2:


Instead of writing lots of code to have Java be executable in source form, you have a few options:

Use Scala! Did you know that Scala is built off Java? It has an interpreter and compiler. You can run a script, a shell or compile and run it. Scala and Java work seamlessly together. Both compile to the same bytecode and run on a JVM. Yes, the language would feel weird because Scala is like a cross between Java, R and Python, but most of the core language is unchanged and all Java packages are available. Give Scala a try. If you are on Linux, you might as well look at Spark too, even for one machine.

If you insist on using Java only, you can create a hybrid program that does two things (I've done this before): compile code and run it. The executable or even a bash script can do the work of taking source files and making them executable. If you are looking to make a Java shell, then you need to make a dynamic runtime compiler/loader, but you simply need to use what Java/Oracle already gives us. Imagine inserting Java syntax from a file where I put a print statement. You could have anything in there you wanted as long as it compiles. See this example:

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:


No. It's not possible to put a she bang on any script and it will execute. Bash relies on the fact that the file with the shebang ignores lines starting with #. Hence any scripting language or byte code that can ignore the first line with the shebang will work.

If your language doesn't support # as comment or ignores the first line needs to go through another scripting language that ignores that.

That being said you can have bash scripts that have binary blobs inline that can be called. Game installers do so.




回答4:


After two and a half years I have stumbled upon a more complete answer than was given in 2016. The Java binaries can be embedded within the executable, contrary to John Hascall's answer. This article explains how this can be done in linux and unix like systems by adding a binary payload to a shell script.

I will offer a short summary of the procedure.

Given an executable jar named any_java_executable.jar
Given you want to create an executable named my_executable
Given a script file named basis.sh with the following contents

#!/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

The native executable can be created by running the following two commands.

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

Then my_executable is a native executable capable of running the java program without depending on the location of the jar file. It can be executed by running

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

and can be used anywhere as a CLI tool if placed in /usr/local/bin.




回答5:


Since JDK11 you can do it directly with source code:

#!/usr/lib/jvm/jdk-11/bin/java --source 8

public class Oneliner {
  public static void main(String[] args){
    System.out.println("ok");
  }
}

Note, that --source parameter is mandatory if file extension is not .java. Values 6-11 are supported, but 6 is marked as deprecated.



来源:https://stackoverflow.com/questions/35620908/is-it-possible-to-make-a-java-executable

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!