ps:
需要实现一个简单的oj功能,想到在java项目中运行cmd命令直接编译、运行java文件,测试类运行正常,然而项目运行起来,编译正常,运行Main文件就提示无法加载类,应该是SpringMVC的类加载机制问题,不会搞,有知道大佬可以留个言。
后来想到用Docker容器运行,果然可行,而且也保证了代码运行的安全性。
一、首先去官网下载Docker Toolbox
https://docs.docker.com/toolbox/
二、下载完后运行安装包直接一直点击下一步
ps:如果电脑安装过git这一步可以不勾选
三、安装完成打开Docker QuickstartTerminal,如果上一步没有勾选git,需要手动选择git的bash.exe路径
四、第一次打开需要进行虚拟机初始化,稍等一会。
出现下面界面即启动成功
五、运行Docker run -it -u root openjdk:8 /bin/bash 安装openjdk环境
六、将本地文件挂载共享到容器(使用默认的C:/Users可跳过该步骤)
双击打开
打开设置-》共享文件夹-》右上角添加,选择本地路径、填写共享名称,后面代码要用到该名称(即下图的c/Users、share)
设置完成后最好将虚拟机关机,然后打开Docekr QuickStart完成重新加载。
七、测试一下
在共享文件夹目录下(以默认C:/Users为例),放一个Main.java文件,输出helloworld。
public class Main {
public static void main(String[] args){
System.out.println("HelloWorld!");
}
}
在该文件目录下打开cmd,输入
Docker run --rm -u root -v /c/Users/OnlineJudge:/c/Users/OnlineJudge openjdk:8 /bin/sh -c cd /c/Users/OnlineJudge&&javac Main.java
其中--rm运行完即删除容器
-v /c/Users/OnlineJudge:/c/Users/OnlineJudge 是挂载命令,将本地文件C:/Users/OnlineJudge挂载到容器中的/c/Users/OnlineJudge下
/bin/sh -c cd /c/Users/OnlineJudge&&javac Main.java是容器运行后输入的命令,cd到挂载的文件目录并且运行命令javac Main.java编译,运行成功无任何回应,文件夹目录下多出Main.class文件
这时候再输入
Docker run --rm -u root -v /c/Users/OnlineJudge:/c/Users/OnlineJudge openjdk:8 /bin/sh -c cd /c/Users/OnlineJudge&&java Main
运行Main,输出Helloworld,至此工作正常。
八、代码编写
需要在java项目中运行cmd命令,这里用到CMDUtil,网上copy。
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
/**
* CMD工具类
*/
public class CMDUtil {
private CMDUtil() {
}
/**
*
* 执行CMD命令
* @param
* @return String
* @throws
*
* *cmd /c dir 是执行完dir命令后关闭命令窗口
cmd /k dir 是执行完dir命令后不关闭命令窗口
cmd /c start dir 会打开一个新窗口后执行dir命令,原窗口会关闭
cmd /k start dir 会打开一个新窗口后执行dir命令,原窗口不会关闭
cmd /? 查看帮助信息
使用标准输出流读不到数据,错误输出流有数据
进程使用这些流来提供到子进程的输入和获得从子进程的输出。因为有些本机平台仅针对标准输入和输出流提供有限的缓冲区大小,
如果读写子进程的输出流或输入流迅速出现失败,则可能导致子进程阻塞,甚至产生死锁,需要异步清空流
*/
public static String exec(String command) {
String executeCommand ="cmd /c "+command;
String rs = "";
Process cmdProcess = null;
try {
cmdProcess = Runtime.getRuntime().exec(executeCommand);
int waitFor = cmdProcess.waitFor();
//把标准的输出流和错误流都输出出来
rs += CmdExecuteResultInfo(cmdProcess.getInputStream());
rs = CmdExecuteResultInfo(cmdProcess.getErrorStream());
cmdProcess.getOutputStream().close();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
clearInBackground(cmdProcess.getInputStream());
clearInBackground(cmdProcess.getErrorStream());
}
return rs;
}
/**
*采用ProcessBuilder来管理进程,会自动清空 builder.redirectErrorStream(true);流
@date 2018年9月12日 下午10:59:36
@email 940945444@qq.com
@return
@param
*/
public static String execProcessBuider(String... command) {
String rs = "";
//指定读取出来的数据编码,如果是
String charset = "UTF-8";
try {
List cmdList = new ArrayList();
for (int i=1;i<command.length;i++){
cmdList.add(command[i]);
}
ProcessBuilder builder = new ProcessBuilder(cmdList);
builder.directory(new File(command[0]));
builder.redirectErrorStream(true);
Process cmdProcess = builder.start();
rs = CmdExecuteResultInfo(cmdProcess.getInputStream());
cmdProcess.getOutputStream().close();
} catch (IOException e) {
e.printStackTrace();
}
return rs;
}
/**
*
* cmd返回的信息
* @param
* @return String
* @throws
*/
private static String CmdExecuteResultInfo(InputStream inputStream) {
StringBuilder builder = new StringBuilder();
BufferedReader cmdExecuteInfoReader = null;
try {
cmdExecuteInfoReader = new BufferedReader(new InputStreamReader(inputStream,"GBK"));
String cmdExecuteInfoLine;
while ((cmdExecuteInfoLine = cmdExecuteInfoReader.readLine()) != null) {
builder.append(cmdExecuteInfoLine);
builder.append("\n");
}
return builder.toString();
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if (cmdExecuteInfoReader != null) {
cmdExecuteInfoReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
*清空流,必须要使用异步来清空
@Author Winston.Wang
@date 2018年9月12日 下午10:58:53
@email 940945444@qq.com
@return
@param
*/
static void clearInBackground(final InputStream is) {
new Thread(new Runnable(){
public void run(){
try{
is.close();
} catch(IOException e){
e.printStackTrace();
}
}
}).start();
}
}
另外再编写JavaComplie.java运行Docker命令
package com.fh.util.OlineJudge;
import com.fh.util.FileUtil;
import com.fh.util.PropUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.ResourceUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class JavaCompile {
private static String ONLINE_JUDGE_DIR = PropUtil.getONLINE_JUDGE_DIR();
private static String ONLINE_JUDGE_SHARE = PropUtil.getONLINE_JUDGE_SHARE();
public static String compile(String id,String writeStr,String path) throws IOException {
writeStr ="import java.io.IOException;import java.io.FileInputStream;"+writeStr;
writeStr = writeStr.replaceAll("(String\\[\\] args\\))(.*)(\\{)","String[] args) throws Exception {");
writeStr = writeStr.replaceAll("(throws Exception \\{)","throws Exception{\n FileInputStream fis=new FileInputStream(\""+path+"\");\n System.setIn(fis);");
String filePath =ONLINE_JUDGE_DIR+id+"\\Main.java";
FileUtil.writeFile(filePath, writeStr);
CMDUtil.execProcessBuider(ONLINE_JUDGE_DIR+id+"\\","Docker","run","--rm","-u","root","-v",String.format("%s:%s",ONLINE_JUDGE_SHARE,ONLINE_JUDGE_SHARE),"openjdk:8","/bin/sh","-c",String.format("cd %s&&javac Main.java",ONLINE_JUDGE_SHARE+"tempJava/"+id));
String exec = CMDUtil.execProcessBuider(ONLINE_JUDGE_DIR+id,"Docker","run","--rm","-i","-u","root","-v",String.format("%s:%s",ONLINE_JUDGE_SHARE,ONLINE_JUDGE_SHARE),"openjdk:8","/bin/sh","-c",String.format("cd %s&&java Main",ONLINE_JUDGE_SHARE+"tempJava/"+id),"TIMEOUT /T 3");
new File(filePath).delete();
new File(filePath.replace(".java",".class")).delete();
new File(ONLINE_JUDGE_DIR+id).delete();
return exec.replaceAll("[\b\r\n\t]*", "");
}
}
主要是两行调用CMDUtil的代码,第一个参数为cmd运行目录,必须是要编译java的所在目录,是本地目录形式(如C:/User/OnlineJudge)后面的命令就是之前Docker编译运行java的命令。