我的理解是这样的,
问题陈述:
编写一个 Java 程序,它应该能够
1.将输入作为Java程序文件
2. 编译,如果不能编译会报错
3.运行上一步生成的class文件
4. 产生调用产生的运行时异常(如果有)
假设:
1. 类将包含一个“main”方法,否则它不能使用“java”程序运行
鉴于上述问题陈述,我已经提出了一个解决方案。在编写代码之前,我认为按照它的执行顺序来解释它的作用是个好主意。
解决步骤:
1. 使用编译器 API 编译 Java 代码(使用 ToolProvider.getSystemJavaCompiler())
2. 使用 DiagnosticCollector 收集可能发生的任何编译错误。
3. 如果编译成功,则将生成的类加载到字节数组中。
4. 使用 ClassLoader.defineClass() 将类文件从字节数组加载到 JVM 运行时。
5.一旦类被加载,使用反射查找main方法,如果不存在则抛出main not found相关异常。
6. 运行 main 方法,并报告任何产生的运行时异常。
注意:如果需要,可以将标准输入和输出流重定向到
新程序和原始对象可以保存为原始对象
主程序。我没有做过,但做起来很简单。
工作代码:
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
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 JreMain {
private static final String PATH_TO_JAVA_FILE = "src/main/java/MyProgram.java";
public static void main(String[] args) {
JreMain main = new JreMain();
System.out.println("Running a java program");
String filePath = PATH_TO_JAVA_FILE;
File javaFile = new File(filePath);
/*compiling the file */
List<String> errorList = main.compile(Arrays.asList(javaFile));
if(errorList.size() != 0) {
System.out.println("file could not be compiled, check below for errors");
for(String error : errorList) {
System.err.println("Error : " + error);
}
} else {
main.runJavaClass(filePath, new String[] {});
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
private void runJavaClass(String filePath, String[] mainArguments) {
System.out.println("Running " + filePath);
ClassLoader classLoader = getClass().getClassLoader();
Class klass = null;
String fileNameWithoutExtension = filePath.substring(0, filePath.length() - ".java".length());
String className = getClassName(fileNameWithoutExtension);
/* loading defineClass method in Classloader through reflection, since it's 'protected' */
try {
/* signature of defineClass method: protected final Class<?> defineClass(String name, byte[] b, int off, int len)*/
Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE);
defineClassMethod.setAccessible(true);
/* attempting to load our class in JVM via byte array */
byte[] classBytes = getClassBytes(fileNameWithoutExtension + ".class");
klass = (Class)defineClassMethod.invoke(classLoader, className, classBytes, 0, classBytes.length);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
/* checking if main method exists, in the loaded class, and running main if exists*/
if(klass != null) {
try {
Method mainMethod = klass.getMethod("main", String[].class);
Class returnType = mainMethod.getReturnType();
/*Checking for main method modifiers and return type*/
if( !Modifier.isStatic(mainMethod.getModifiers()) || !Modifier.isPublic(mainMethod.getModifiers()) || !(returnType.equals(Void.TYPE) || returnType.equals(Void.class))) {
throw new RuntimeException("Main method signature incorrect, expected : \"public static void main(String[] args)\",");
}
/* finally invoking the main method **/
mainMethod.invoke(null, new Object[]{mainArguments});
} catch (NoSuchMethodException e) {
throw new RuntimeException("Class " + klass.getCanonicalName() + " does not declare main method");
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
/*e.printStackTrace();*/
System.err.println("Exception in main :");
throw new RuntimeException(e.getCause());
}
}
}
private String getClassName(String fileNameWithoutExtension) {
String className = null;
int lastIndex = -1;
if( ( lastIndex = fileNameWithoutExtension.lastIndexOf(File.separator)) != -1) {
className = fileNameWithoutExtension.substring(lastIndex + 1);
} if( ( lastIndex = fileNameWithoutExtension.lastIndexOf("\\")) != -1) {
className = fileNameWithoutExtension.substring(lastIndex + 1);
} else if( ( lastIndex = fileNameWithoutExtension.lastIndexOf("/")) != -1) {
className = fileNameWithoutExtension.substring(lastIndex + 1);
}
return className;
}
private byte[] getClassBytes(String classFilePath) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
File classFile = new File(classFilePath);
if(!classFile.exists()) {
throw new RuntimeException("Class file does not exist : " + classFile.getAbsolutePath());
}
byte[] buffer = new byte[2048];
int readLen = -1;
FileInputStream fis = null;
try {
fis = new FileInputStream(classFile);
while( (readLen = fis.read(buffer)) != -1) {
baos.write(buffer, 0, readLen);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return baos.toByteArray();
}
@SuppressWarnings("restriction")
public List<String> compile (List<File> javaFileList) {
System.out.println("Started compilation");
List<String> errorList = new ArrayList<String>();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(
diagnostics, null, null);
Iterable<? extends JavaFileObject> compilationUnits = fileManager
.getJavaFileObjectsFromFiles(javaFileList);
compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits)
.call();
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics
.getDiagnostics()) {
String diagnosticMessage = String.format("Error on line %d in %s%n",
diagnostic.getLineNumber(), diagnostic.getSource().toUri() + " : \n\t" + diagnostic.getMessage(null));
/*Following gives out of box good message, but I used above to show the custom use of diagnostic
* String diagnosticMessage = diagnostic.toString();*/
errorList.add(diagnosticMessage);
}
try {
fileManager.close();
} catch (IOException e) {
e.printStackTrace();
}
return errorList;
}
}
感谢@Miserable 变量,我几乎准备好为 javac 程序创建一个进程,但你的回答为我节省了一些丑陋的代码。
** *已编辑 **
命令行参数
编译:
//in the JreMain.compile()
List<String> compilerOptionsList = Arrays.asList("-classpath", "jar/slf4j-api-1.7.10.jar", "-verbose");
JavaCompiler.CompilationTask compilationTask = compiler.getTask(null,
fileManager, diagnostics, compilerOptionsList, null,
compilationUnits);
对于 Java 运行时参数:
They will have to be passed to our JareMain program itself.
java -classpath "jar/slf4j-api-1.7.10.jar;" -verbose JreMain
对于新程序的main方法:
//second argument here is going to main method
main.runJavaClass(filePath, new String[] {});