[关闭]
@snail-lb 2017-02-28T15:22:38.000000Z 字数 13003 阅读 1594

JavaOJ 判题核心系统(使用沙盒实现)

java进阶


一、加载系统中的Class文件并执行其中的方法

(使用多线程来执行系统中的Class文件,这个类中的方法有参数,有返回值)

1.自定义类加载器

  1. package com.cn.oj_java_my;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. public class MyClassLoader extends ClassLoader {
  5. /** 默认classPath */
  6. private String classPath;
  7. /** class的实例 **/
  8. private Class<?> cla;
  9. /**
  10. * 构造函数
  11. * @param classPath
  12. */
  13. public MyClassLoader(String classPath) {
  14. this.classPath = classPath;
  15. }
  16. /**
  17. * @param className 类的名称,不支持带有包名的类
  18. */
  19. @Override
  20. protected Class<?> findClass(String className) throws ClassNotFoundException {
  21. return loadClass(classPath, className);
  22. }
  23. /**
  24. * 更改类加载器加载类的classpath,在指定路径下加载制定的类class文件
  25. *
  26. * @param classPath
  27. * 要加载的类路径
  28. * @param className
  29. * 要加载的类名 只能加载不含包的类.
  30. * @throws ClassNotFoundException
  31. * @throws IllegalAccessException
  32. * @throws InstantiationException
  33. */
  34. public Class<?> loadClass(String classPath, String className)
  35. throws ClassNotFoundException{
  36. File classFile = new File(classPath + "\\" + className + ".class");
  37. byte[] mainClass = new byte[(int) classFile.length()];
  38. try {
  39. FileInputStream in = new FileInputStream(classFile);
  40. in.read(mainClass);
  41. in.close();
  42. } catch (Exception e) {
  43. e.printStackTrace();
  44. throw new ClassNotFoundException(className);
  45. }
  46. Class<?> cla = super.defineClass(className, mainClass, 0, mainClass.length);
  47. this.cla = cla;
  48. return cla;
  49. }
  50. /**
  51. * 获取classPath
  52. *
  53. * @return String classPath
  54. */
  55. public String getClassPath() {
  56. return classPath;
  57. }
  58. /**
  59. * 实例化当前类 此方法必须在类加载之后执行
  60. * @param className
  61. * @return
  62. * @throws InstantiationException
  63. * @throws IllegalAccessException
  64. * @throws ClassNotFoundException
  65. */
  66. public Object newInstance(String className)
  67. throws InstantiationException, IllegalAccessException, ClassNotFoundException {
  68. if(this.cla == null){
  69. throw new RuntimeException("类" + className + "没有加载");
  70. }
  71. return this.cla.newInstance();
  72. }
  73. }

2.MyObject

  1. package com.cn.oj_java_my;
  2. import java.lang.reflect.Method;
  3. /**
  4. * 通过反射获取类中响应的方法属性
  5. *
  6. * @author lvbiao
  7. *
  8. */
  9. public class MyObject {
  10. private String classPath;
  11. private String className;
  12. /**自定义类加载器**/
  13. private MyClassLoader mcl;
  14. //类初始化
  15. public void init(String classPath,String className){
  16. this.classPath = classPath;
  17. this.className = className;
  18. mcl = new MyClassLoader(classPath);
  19. }
  20. public Method getMethod(String methodName) throws Exception {
  21. if(mcl == null){
  22. throw new RuntimeException(this.className + "没有初始化");
  23. }
  24. Class<?> cla = mcl.findClass(className);
  25. Method method = cla.getMethod(methodName, String.class);
  26. method.setAccessible(true);
  27. return method;
  28. }
  29. public Object newInstance() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
  30. return mcl.newInstance(className);
  31. }
  32. }

3.MyTask

  1. package com.cn.oj_java_my;
  2. import java.lang.reflect.Method;
  3. import java.util.concurrent.Callable;
  4. public class MyTask implements Callable<Object> {
  5. private String parameters;
  6. private String classPath;
  7. private String className;
  8. public MyTask(String parameters) {
  9. this.parameters = parameters;
  10. }
  11. @Override
  12. public Object call() throws Exception {
  13. // 这里使用反射获取main函数
  14. MyObject mo = new MyObject();
  15. if(classPath.equals(null)){
  16. throw new RuntimeException("没有设置类路径");
  17. }
  18. if(className.equals(null)){
  19. throw new RuntimeException("没有设置类名称");
  20. }
  21. mo.init(classPath, className);
  22. //要获取的方法名称
  23. String methodName = "test";
  24. Method mainMethod = mo.getMethod(methodName);
  25. //获取实例化的类
  26. Object obj = mainMethod.invoke(mo.newInstance(), parameters);
  27. return obj;
  28. }
  29. public String getClassPath() {
  30. return classPath;
  31. }
  32. public void setClassPath(String classPath) {
  33. this.classPath = classPath;
  34. }
  35. public String getClassName() {
  36. return className;
  37. }
  38. public void setClassName(String className) {
  39. this.className = className;
  40. }
  41. }

4.MyOjCore.java

  1. package com.cn.oj_java_my;
  2. import java.util.concurrent.ExecutionException;
  3. import java.util.concurrent.ExecutorService;
  4. import java.util.concurrent.Executors;
  5. import java.util.concurrent.Future;
  6. import java.util.concurrent.TimeUnit;
  7. public class MyOjCore {
  8. public static void main(String[] args) {
  9. //设置线程池
  10. ExecutorService executor = Executors.newCachedThreadPool();
  11. //设置方法参数
  12. String parameters = "asdf";
  13. MyTask task = new MyTask(parameters);
  14. task.setClassPath("F:\\Test");
  15. task.setClassName("Test");
  16. Future<Object> result = executor.submit(task);
  17. executor.shutdown();
  18. try {
  19. TimeUnit.MILLISECONDS.sleep(3000);
  20. } catch (InterruptedException e1) {
  21. e1.printStackTrace();
  22. }
  23. System.out.println("主线程在执行任务");
  24. try {
  25. System.out.println("task运行结果" + result.get().toString());
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. } catch (ExecutionException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. }

5.系统中F:\Test\Test.java文件

  1. public class Test {
  2. public String test(String str){
  3. System.out.println("+++"+str+"+++");
  4. return new String("aaaaaaaaaaa");
  5. }
  6. }

二、完成了对java文件的编译,加载,运行及接管控制台输入功能,

1.编译模块

  1. package com.cn.ojcore.common;
  2. import java.io.BufferedReader;
  3. import java.io.InputStream;
  4. import java.io.InputStreamReader;
  5. /**
  6. * 编译java文件
  7. * @author lvbiao
  8. *
  9. */
  10. public class MyCompiler {
  11. /**
  12. * 批处理文件路径
  13. * @param path
  14. */
  15. public static void command(String path){
  16. try {
  17. Runtime run = Runtime.getRuntime();
  18. //String path = "F:\\Test\\my1.bat";
  19. //String path = "F:\\Test\\my1.bat";
  20. Process process = run.exec("cmd.exe /c " + path);
  21. InputStream in = process.getInputStream();
  22. InputStream inError = process.getErrorStream();
  23. BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
  24. BufferedReader brError = new BufferedReader(new InputStreamReader(inError, "GBK"));
  25. String error;//错误信息
  26. while ((error = brError.readLine()) != null) {
  27. System.out.println(error);
  28. }
  29. String s;
  30. while ((s = bufferedReader.readLine()) != null) {
  31. System.out.println(s);
  32. }
  33. in.close();
  34. bufferedReader.close();
  35. System.out.println("返回值:" + process.waitFor());
  36. System.out.println("========================");
  37. } catch (Exception e) {
  38. e.printStackTrace();
  39. }
  40. }
  41. }

2.自定义加载模块

  1. package com.cn.ojcore.common;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. public class MyClassLoader extends ClassLoader {
  5. /** 默认classPath */
  6. private String classPath;
  7. /** class的实例 **/
  8. private Class<?> cla;
  9. /**
  10. * 构造函数
  11. * @param classPath
  12. */
  13. public MyClassLoader(String classPath) {
  14. this.classPath = classPath;
  15. }
  16. /**
  17. * @param className 类的名称,不支持带有包名的类
  18. */
  19. @Override
  20. protected Class<?> findClass(String className) throws ClassNotFoundException {
  21. return loadClass(classPath, className);
  22. }
  23. /**
  24. * 更改类加载器加载类的classpath,在指定路径下加载制定的类class文件
  25. *
  26. * @param classPath
  27. * 要加载的类路径
  28. * @param className
  29. * 要加载的类名 只能加载不含包的类.
  30. * @throws ClassNotFoundException
  31. * @throws IllegalAccessException
  32. * @throws InstantiationException
  33. */
  34. public Class<?> loadClass(String classPath, String className)
  35. throws ClassNotFoundException{
  36. File classFile = new File(classPath + "\\" + className + ".class");
  37. byte[] mainClass = new byte[(int) classFile.length()];
  38. try {
  39. FileInputStream in = new FileInputStream(classFile);
  40. in.read(mainClass);
  41. in.close();
  42. } catch (Exception e) {
  43. e.printStackTrace();
  44. throw new ClassNotFoundException(className);
  45. }
  46. Class<?> cla = super.defineClass(className, mainClass, 0, mainClass.length);
  47. this.cla = cla;
  48. return cla;
  49. }
  50. /**
  51. * 获取classPath
  52. *
  53. * @return String classPath
  54. */
  55. public String getClassPath() {
  56. return classPath;
  57. }
  58. /**
  59. * 实例化当前类 此方法必须在类加载之后执行
  60. * @param className
  61. * @return
  62. * @throws InstantiationException
  63. * @throws IllegalAccessException
  64. * @throws ClassNotFoundException
  65. */
  66. public Object newInstance(String className)
  67. throws InstantiationException, IllegalAccessException, ClassNotFoundException {
  68. if(this.cla == null){
  69. throw new RuntimeException("类" + className + "没有加载");
  70. }
  71. return this.cla.newInstance();
  72. }
  73. }

3.MyObject

  1. package com.cn.ojcore.common;
  2. import java.lang.reflect.Method;
  3. /**
  4. * 通过反射获取类中响应的方法属性
  5. *
  6. * @author lvbiao
  7. *
  8. */
  9. public class MyObject {
  10. private String classPath;
  11. private String className;
  12. /**自定义类加载器**/
  13. private MyClassLoader mcl;
  14. //类初始化
  15. public void init(String classPath,String className){
  16. this.classPath = classPath;
  17. this.className = className;
  18. mcl = new MyClassLoader(classPath);
  19. }
  20. public Method getMethod(String methodName) throws Exception {
  21. if(mcl == null){
  22. throw new RuntimeException(this.className + "没有初始化");
  23. }
  24. Class<?> cla = mcl.findClass(className);
  25. // Method method = cla.getMethod(methodName, (new Object[0]).getClass());
  26. Method method = cla.getMethod(methodName, String[].class);
  27. method.setAccessible(true);
  28. return method;
  29. }
  30. public Object newInstance() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
  31. return mcl.newInstance(className);
  32. }
  33. }

4.MyTask.java

  1. package com.cn.ojcore.common;
  2. import java.io.BufferedInputStream;
  3. import java.io.ByteArrayInputStream;
  4. import java.io.Console;
  5. import java.io.DataInputStream;
  6. import java.io.IOException;
  7. import java.io.InputStream;
  8. import java.io.ObjectInputStream;
  9. import java.io.OutputStream;
  10. import java.io.PrintStream;
  11. import java.lang.reflect.Method;
  12. import java.lang.reflect.Modifier;
  13. import java.util.Scanner;
  14. import java.util.concurrent.Callable;
  15. public class MyTask implements Callable<Object> {
  16. private String[] parameters;
  17. private String classPath;
  18. private String className;
  19. private String methodName;
  20. public MyTask(String classPath, String className, String methodName) {
  21. this.classPath = classPath;
  22. this.className = className;
  23. this.methodName = methodName;
  24. }
  25. // 设置参数
  26. public void setParameter(String[] parameters) {
  27. this.parameters = parameters;
  28. }
  29. public Object call() throws Exception{
  30. return this.invoke();
  31. }
  32. // 有参数的方法
  33. public Object invoke() throws Exception {
  34. // 这里使用反射获取函数
  35. MyObject mo = new MyObject();
  36. //初始化,设置类路径及类名称
  37. mo.init(classPath, className);
  38. Method mainMethod = mo.getMethod(methodName);
  39. Object obj;
  40. //调用方法,获取返回值
  41. if(Modifier.isStatic(mainMethod.getModifiers())) {
  42. //静态方法的调用
  43. // obj = mainMethod.invoke(null, new Object[] { parameters });
  44. //输入重定向
  45. System.setIn(new BufferedInputStream(new ByteArrayInputStream("abc aaa".getBytes())));
  46. obj = mainMethod.invoke(null, (Object)parameters);
  47. }else{
  48. //非静态方法的调用
  49. // obj = mainMethod.invoke(mo.newInstance(), new Object[] { parameters });
  50. obj = mainMethod.invoke(mo.newInstance(), (Object)parameters);
  51. }
  52. return obj;
  53. }
  54. }

5.MyOjCore.java

  1. package com.cn.ojcore;
  2. import java.util.concurrent.ExecutionException;
  3. import java.util.concurrent.ExecutorService;
  4. import java.util.concurrent.Executors;
  5. import java.util.concurrent.Future;
  6. import com.cn.ojcore.common.MyCompiler;
  7. import com.cn.ojcore.common.MyTask;
  8. public class MyOjCore {
  9. /**类路径**/
  10. private static String classPath = "F:\\mavenProject\\MyOjCore\\Test";
  11. /**类名称**/
  12. private static String className = "Test2";
  13. /**调用方法名称**/
  14. private static String methodName = "main";
  15. /**批操作文件文件名**/
  16. private static String batName = "mytest.bat";
  17. public static void main(String[] args) {
  18. //设置线程池
  19. ExecutorService executor = Executors.newCachedThreadPool();
  20. //编译java文件
  21. MyCompiler.command(classPath + "\\" + batName);
  22. //设置方法参数
  23. String[] parameters = {"Bob","Tom"};
  24. MyTask task = new MyTask(classPath, className, methodName);
  25. task.setParameter(parameters);
  26. Future<Object> result = executor.submit(task);
  27. executor.shutdown();
  28. try {
  29. Object obj = result.get();
  30. if(obj == null){
  31. System.out.println("返回值为空,没有返回值");
  32. }else{
  33. System.out.println("task运行结果" + result.get().toString());
  34. }
  35. } catch (InterruptedException e) {
  36. e.printStackTrace();
  37. } catch (ExecutionException e) {
  38. e.printStackTrace();
  39. }
  40. }
  41. }

6.Test.java

  1. public class Test2 {
  2. public static void main(String[] args){
  3. java.util.Scanner ac = new java.util.Scanner(System.in);
  4. String str1 = ac.nextLine();
  5. System.out.println(str1);
  6. }
  7. }

7.mytest.bat

  1. F:
  2. cd F:\mavenProject\MyOjCore\Test
  3. javac Test2.java

8.运行结果

F:\mavenProject\MyOjCore>F:
F:\mavenProject\MyOjCore>cd F:\mavenProject\MyOjCore\Test
F:\mavenProject\MyOjCore\Test>javac Test2.java
返回值:0
++++++++++++++++++++
abc aaa
返回值为空,没有返回值

三、后续

后续代码较多,欢迎访问
我的git

四、个人总结

在整个java判题内核的编写过程中,主要有以下几个技术难点,现在对这些做一个总结,以便以后进行查阅。
1.获取系统的输入输出。
比如现在我要运行一个用户提交的程序,平常我们都是在Eclipse中直接运行,如果有运行中的输入输出,我们可以直接在控制台进行输入输出,但是现在这个程序托管给了我们,需要我们自己进行运行,这就需要我们在程序中提前将输入设置好,在运行之前将其输入。这就需要用到System.setIn(),System.setOut()等方法,具体用法见文档。

  1. System.setIn(new BufferedInputStream(new ByteArrayInputStream(normInputBuffer.toString().getBytes())));
  2. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  3. System.setOut(new PrintStream(baos));

2.执行cmd命令
java文件的编译工作需要cmd控制台中执行javac命令进行,执行cmd命令方式:

  1. Runtime run = Runtime.getRuntime();
  2. Process process = run.exec("cmd.exe /c " + order);

3.class文件的导入
由于我们的class文件是在程序执行过程中生成的,所以需要我们手动的将class文件导入到jvm中,导入方法:
我们的导入类继承ClassLoader类,然后调用父类中defineClass方法

  1. public class MyClassLoader extends ClassLoader {
  2. public Class<?> loadClass(byte[] classFile, String className) {
  3. Class<?> cla = super.defineClass(className, classFile, 0, classFile.length);
  4. return cla;
  5. }
  6. }

另外还需要注意,不同进程中的类是不可以通用的,就是说Class对象是不能通过socket传送的。

4.安全
在执行用户代码的时候,我们需要将其放入到沙箱中进行执行,并给与最低权限,以防发生安全事故。设置安全管理器:

  1. security = System.getSecurityManager();
  2. if (security == null) {
  3. System.setSecurityManager(new MySecurityManager());
  4. }

自定义安全策略:

  1. public class MySecurityManager extends SecurityManager{
  2. /**
  3. * 重写强行退出检测
  4. * 防止用户自行终止虚拟机的运行,但是调用程序端可以执行退出
  5. * */
  6. public void checkExit(int status) {
  7. throw new SecurityException("Exit On Client Is Not Allowed!");
  8. }
  9. /**
  10. * 策略权限查看
  11. * 当执行操作时调用,如果操作允许则返回,操作不允许抛出SecurityException
  12. * */
  13. private void sandboxCheck(Permission perm) throws SecurityException {
  14. // 设置只读属性
  15. if (perm instanceof SecurityPermission) {
  16. //安全性
  17. if (perm.getName().startsWith("getProperty")) {
  18. return;
  19. }
  20. } else if (perm instanceof PropertyPermission) {
  21. //属性
  22. if (perm.getActions().equals("read")) {
  23. return;
  24. } else{
  25. throw new SecurityException(perm.toString());
  26. }
  27. } else if (perm instanceof FilePermission) {
  28. //文件操作
  29. if (perm.getActions().equals("read") || perm.getActions().equals("execute")) {
  30. return;
  31. }else{
  32. throw new SecurityException(perm.toString());
  33. }
  34. } else if (perm instanceof ReflectPermission){
  35. //反射
  36. return;
  37. }else if(perm instanceof RuntimePermission){
  38. //运行时安全
  39. return;
  40. }
  41. else if(perm instanceof SocketPermission){
  42. //Socket权限
  43. if (perm.getActions().equals("accept") ||
  44. perm.getActions().equals("connect") ||
  45. perm.getActions().equals("listen") ||
  46. perm.getActions().equals("resolve")
  47. ) {
  48. return;
  49. }
  50. }else if(perm instanceof NetPermission){
  51. //网络
  52. if(perm.implies(new NetPermission("specifyStreamHandler"))){
  53. //在构造 URL 时指定流处理程序的能力
  54. return;
  55. }else{
  56. throw new SecurityException(perm.toString());
  57. }
  58. }else{
  59. throw new SecurityException(perm.toString());
  60. }
  61. }
  62. @Override
  63. public void checkPermission(Permission perm) {
  64. this.sandboxCheck(perm);
  65. }
  66. @Override
  67. public void checkPermission(Permission perm, Object context) {
  68. this.sandboxCheck(perm);
  69. }
  70. }

5.定时任务
用户程序执行需要有时间限制,所以就有的定时任务,定时任务一般使用FutureTask实现,FutureTask是可取消的异步计算。FutureTask需要放入到Thread中然后Thread进行开始。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注