一、前言
相信大家都在各种网站做过算法题。
而在那些网站通过文本框提交代码之后的即时编译运行是怎么做到的呢。
二、思路
注:这篇文章的方法只适用于java(不过能生成class文件的语言也都能套用)
其实一开始要实现这个功能是相当迷茫的,在网上搜不知道该搜什么,也包括这篇文章起标题名都不知道该叫什么。
查了一会果断开始自己造轮子了。
方案1
在自己的web上写一套可以跑java语言的编译器和解释器。
显然方案1不太可行(时间成本略高),不过也不排除有套件可以用。
方案2
让客户自己生成java或者class文件提交上来,在服务器运行。
方案2的话,用户自己编译文件提交文件,前提还得用户有java环境。
方案3
客户提交String形式的代码,服务器将String编译成class,然后服务器运行。
只有3是最亲民的方案了。
三、实现
找到思路之后,剩下的就是凑代码了。
刚好本身又了解一些相关的知识。
简单来说只要实现两个东西:
1.从request请求中获取用户的代码串,编译成class文件。
2.使用ClassLoader加载class文件。
为什么用自定义的ClassLoader而不用class.forName()
不知道的话就百度吧,建议了解。
对代码就不做详细的解释了。简单直白。
MyCompiler.java
package compile;
import classLoader.MyClassLoader;
import javax.tools.JavaCompiler;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class MyCompiler {
static String outDir = System.getProperty("user.dir") + "\\src";
public void compile(String name, String content) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
StrSrcJavaObject srcObject = new StrSrcJavaObject(name, content);
List<StrSrcJavaObject> fileObjects = Collections.singletonList(srcObject);
String flag = "-d";
System.out.println("输出目录为" + outDir);
Iterable<String> options = Arrays.asList(flag, outDir);
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, options, null, fileObjects);
boolean result = task.call();
if (result) System.out.println("Compile it successfully.");
}
private static class StrSrcJavaObject extends SimpleJavaFileObject {
private final String content;
public StrSrcJavaObject(String name, String content) {
super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
this.content = content;
}
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return content;
}
}
}
MyClassLoader.java
package classLoader;
import java.io.*;
public class MyClassLoader extends ClassLoader {
private final String classpath;
public MyClassLoader() {
this.classpath = System.getProperty("user.dir") + "\\src";
}
public MyClassLoader(String classpath) {
this.classpath = classpath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] classDate = getDate(name);
//defineClass方法将字节码转化为类
if (classDate != null) return defineClass(name, classDate, 0, classDate.length);
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
//返回类的字节码
private byte[] getDate(String className) throws IOException {
String path = classpath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
try (InputStream in = new FileInputStream(path); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
byte[] buffer = new byte[2048];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return out.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
TestServlet.java
package mservlet;
import classLoader.MyClassLoader;
import compile.MyCompiler;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String opt = req.getParameter("opt");
String content = req.getParameter("content");
if(opt.equals("0")){
//String content = "package play; public class Test{ public static void main(String[] args){System.out.println(\"compile test.\");} }";
Class<?> cls = MyCompiler.compile("play.Test", content);
}else{
MyClassLoader myClassLoader = new MyClassLoader();
try {
Class c = myClassLoader.loadClass("play.Test");
Method method = c.getMethod("main", String[].class);
method.invoke(null, new Object[]{new String[]{}});
req.setAttribute("resp","成功运行真棒");
} catch (Exception e) {
e.printStackTrace();
}
}
req.getRequestDispatcher("/index.jsp").forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>testServlet</servlet-name>
<servlet-class>mservlet.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>testServlet</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app>
index.jsp
<%--
Created by IntelliJ IDEA.
User: LICHEN
Date: 2020/5/10
Time: 19:48
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<form action="${pageContext.request.contextPath}/test" METHOD="post">
<input type="submit" value="test">
content<textarea rows="8" name="content"></textarea>
opt<input type="text" name="opt" value="0">
</form>
<h2>${resp}</h2>
<body>
</body>
</html>