Java 动态编译 Java 代码,idea 调试 jar 文件

1. 项目结构

各模块介绍

compile-dao:dao 层,数据库持久化层,本文暂时用不到

compile-pojo:实体类

compile-service:业务逻辑模块

compile-shell:Java 动态编译的一些 java 文件

compile-web:SpringBoot 的入口

springboot-java-compile 父工程 pom.xml

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <packaging>pom</packaging>
  6. <modules>
  7. <module>compile-shell</module>
  8. <module>compile-service</module>
  9. <module>compile-web</module>
  10. <module>compile-dao</module>
  11. <module>compile-pojo</module>
  12. </modules>
  13. <parent>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-parent</artifactId>
  16. <version>2.1.2.RELEASE</version>
  17. <relativePath/> <!-- lookup parent from repository -->
  18. </parent>
  19. <groupId>com.wjx</groupId>
  20. <artifactId>springboot-java-compile</artifactId>
  21. <version>0.0.1-SNAPSHOT</version>
  22. <name>springboot-java-compile</name>
  23. <description>Demo project for Spring Boot</description>
  24.  
  25. <properties>
  26. <java.version>1.8</java.version>
  27. </properties>
  28.  
  29. <dependencies>
  30. <dependency>
  31. <groupId>org.springframework.boot</groupId>
  32. <artifactId>spring-boot-starter-web</artifactId>
  33. </dependency>
  34. </dependencies>
  35.  
  36. <!--指定使用maven打包-->
  37. <build>
  38. <plugins>
  39. <plugin>
  40. <groupId>org.apache.maven.plugins</groupId>
  41. <artifactId>maven-compiler-plugin</artifactId>
  42. <version>3.1</version>
  43. <configuration>
  44. <source>${java.version}</source>
  45. <target>${java.version}</target>
  46. </configuration>
  47. </plugin>
  48.  
  49. <plugin>
  50. <groupId>org.apache.maven.plugins</groupId>
  51. <artifactId>maven-surefire-plugin</artifactId>
  52. <version>2.19.1</version>
  53. <configuration>
  54. <skipTests>true</skipTests> <!--默认关掉单元测试 -->
  55. </configuration>
  56. </plugin>
  57. </plugins>
  58. </build>
  59.  
  60. </project>
 

 

compile-shell 模块

pom.xml

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>springboot-java-compile</artifactId>
  7. <groupId>com.wjx</groupId>
  8. <version>0.0.1-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11.  
  12. <artifactId>compile-shell</artifactId>
  13.  
  14.  
  15. <dependencies>
  16. <dependency>
  17. <groupId>com.alibaba</groupId>
  18. <artifactId>fastjson</artifactId>
  19. <version>1.2.54</version>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.projectlombok</groupId>
  23. <artifactId>lombok</artifactId>
  24. </dependency>
  25. </dependencies>
  26. </project>
 

JavaShellExecutor.java

 
  1. package com.compile.executor;
  2.  
  3. import com.compile.shell.*;
  4. import lombok.Data;
  5.  
  6. import java.lang.reflect.Method;
  7.  
  8. /**
  9. * @Description: 获取编译后的结果
  10. * @Auther: wjx
  11. * @Date: 2019/1/29 15:45
  12. */
  13. @Data
  14. public class JavaShellExecutor {
  15.  
  16. /**
  17. * 获取类名,className等于ruleName
  18. *
  19. * @param ruleName
  20. * @return
  21. */
  22. public String getClassName(String ruleName) {
  23. String className = ruleName.replaceAll("[^a-z^A-Z]", "");
  24. return className.substring(0, 1).toUpperCase() + className.substring(1);
  25. }
  26.  
  27. /**
  28. * 获取编译的结果
  29. *
  30. * @param className
  31. * @param classObject
  32. * @param ruleName
  33. * @return
  34. */
  35. public Class<?> getCompileResult(String className, JavaClassObject classObject, String ruleName) {
  36. //使用新的自定义Classloader,记得每个规则使用一个新的classloader,
  37. //当规则更新时,老的classloader可被释放
  38. try {
  39. return getLoadClass(className, classObject, ruleName);
  40. } catch (Throwable e) {
  41. throw new RuntimeException("load class error!" + ",className=" + className + "\r\n" + e.getMessage(), e);
  42. }
  43. }
  44.  
  45. /**
  46. * 获取加载的Class
  47. *
  48. * @param className
  49. * @param classObject
  50. * @param ruleName
  51. * @return
  52. */
  53. public Class<?> getLoadClass(String className, JavaClassObject classObject, String ruleName) {
  54. if (DataEventFactory.eventClassLoaders.get(ruleName) == null) {
  55. DynamicEngine.getInstance().createNewClassLoader(ruleName);
  56. }
  57. DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(DataEventFactory.eventClassLoaders.get(ruleName));
  58. Class aClass = dynamicClassLoader.loadClass(JavaSourceObject.packageName + ruleName, classObject);
  59. return aClass;
  60. }
  61.  
  62. /**
  63. * 执行方法并返回结果
  64. *
  65. * @param aClass
  66. * @param methodName
  67. * @return
  68. */
  69. public Object getMethodResult(Class aClass, String methodName, Object... params) {
  70. Object result = null;
  71. try {
  72. Object instance = aClass.newInstance();
  73. Method method = aClass.getMethod(methodName, String.class);
  74. result = method.invoke(instance, params);
  75. System.out.println(result);
  76. } catch (Exception e) {
  77. e.printStackTrace();
  78. }
  79. return result;
  80. }
  81.  
  82. }
 

ClassFileManager.java

 
  1. package com.compile.shell;
  2.  
  3. import javax.tools.*;
  4. import java.io.IOException;
  5.  
  6. /**
  7. * @Description: 类文件管理器
  8. * @Auther: wjx
  9. * @Date: 2019/1/18 14:22
  10. */
  11. public class ClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
  12.  
  13.  
  14. private JavaClassObject javaClassObject;
  15.  
  16. protected ClassFileManager(StandardJavaFileManager standardJavaFileManager) {
  17. super(standardJavaFileManager);
  18. }
  19.  
  20. public JavaClassObject getJavaClassObject() {
  21. return javaClassObject;
  22. }
  23.  
  24. /**
  25. * 源文件被编译成 .class 文件
  26. *
  27. * @param location
  28. * @param className
  29. * @param kind
  30. * @param sibling
  31. * @return
  32. * @throws IOException
  33. */
  34. @Override
  35. public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
  36. javaClassObject = new JavaClassObject(className, kind);
  37. return javaClassObject;
  38. }
  39. }
 

DataEventFactory.java

 
  1. package com.compile.shell;
  2.  
  3. import java.net.URLClassLoader;
  4. import java.util.Map;
  5. import java.util.concurrent.ConcurrentHashMap;
  6.  
  7. /**
  8. * @Description: 创建一个存放ClassLoad的类
  9. * @Auther: wjx
  10. * @Date: 2019/1/18 15:47
  11. */
  12. public class DataEventFactory {
  13. /**
  14. * 存放当前的ClassLoader
  15. */
  16. public static Map<String, URLClassLoader> eventClassLoaders = new ConcurrentHashMap<>();
  17. }
 

DynamicClassLoader.java

 
  1. package com.compile.shell;
  2.  
  3.  
  4. import java.net.URL;
  5. import java.net.URLClassLoader;
  6.  
  7. /**
  8. * @Description: 读取java文件编译结果
  9. * @Auther: wjx
  10. * @Date: 2019/1/18 15:41
  11. */
  12. public class DynamicClassLoader extends URLClassLoader {
  13.  
  14. public DynamicClassLoader(ClassLoader parent) {
  15. super(new URL[0], parent);
  16. }
  17.  
  18. public Class findClassByName(String className) throws ClassNotFoundException {
  19. return this.findClass(className);
  20. }
  21.  
  22. /**
  23. * 加载读取返回的类
  24. * @param fullName fullName = packageName + className
  25. * @param classObject
  26. * @return
  27. */
  28. public Class loadClass(String fullName, JavaClassObject classObject) {
  29. byte[] bytes = classObject.getBytes();
  30. return this.defineClass(fullName, bytes, 0, bytes.length);
  31. }
  32. }
 

DynamicEngine.java 核心类

 
  1. package com.compile.shell;
  2.  
  3. import com.compile.executor.JavaShellExecutor;
  4.  
  5. import javax.tools.*;
  6. import java.io.File;
  7. import java.net.URL;
  8. import java.net.URLClassLoader;
  9. import java.util.ArrayList;
  10. import java.util.List;
  11.  
  12. /**
  13. * Compile动态执行java的String代码到内存中并执行
  14. * 1.创建 URLClassLoader 类加载器
  15. * 2.获取当前执行的classpath的所有jar包的路径
  16. * 3.通过java的ToolProvider创建JavaCompile,用来执行class源文件
  17. * 4.创建DiagnosticCollector用来执行获取执行失败的错误结果
  18. * 5.添加动态执行的编译环境 options 是个集合,添加内容,字符集,classpath等
  19. * 6.传入JavaFileObject的java文件,是个集合,创建JavaSourceObject实现这个接口,Kind.SOURCE.extension = '.java'
  20. * 7.创建任务并执行
  21. * 8.获取执行完成后的返回JavaClassObject类
  22. * 9.创建DynamicClassLoader来加载类 ,defineClass这个方法
  23. *
  24. * @Description:
  25. * @Auther: wjx
  26. * @Date: 2019/1/18 14:55
  27. */
  28. public class DynamicEngine extends JavaShellExecutor {
  29.  
  30. private static DynamicEngine dynamicEngine;
  31.  
  32. /**
  33. * 单例模式
  34. *
  35. * @return
  36. */
  37. public static DynamicEngine getInstance() {
  38. if (dynamicEngine == null) {
  39. synchronized (DynamicEngine.class) {
  40. if (dynamicEngine == null) {
  41. dynamicEngine = new DynamicEngine();
  42. }
  43. }
  44. }
  45. return dynamicEngine;
  46. }
  47.  
  48. //创建动态加载jar包
  49. private URLClassLoader classLoader;
  50. //当前的classpath环境
  51. private String classpath;
  52.  
  53. private DynamicEngine() {
  54. //获取类加载器
  55. this.classLoader = (URLClassLoader) this.getClass().getClassLoader();
  56.  
  57. this.buildClasspath();
  58. }
  59.  
  60. private void buildClasspath() {
  61. //初始化classpath为null
  62. this.classpath = null;
  63. StringBuilder sb = new StringBuilder();
  64. for (URL url : this.classLoader.getURLs()) {
  65. String f = url.getFile();
  66. sb.append(f).append(File.pathSeparator);
  67. }
  68. this.classpath = sb.toString();
  69. }
  70.  
  71.  
  72. /**
  73. * 编译
  74. *
  75. * @param className
  76. * @param javaCode
  77. * @return
  78. */
  79. public Object javaCodeToObject(String className, String javaCode) {
  80. //记录编译起始时间
  81. long startTime = System.currentTimeMillis();
  82.  
  83. //通过java工具获取编译的compile
  84. JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
  85. //创建DiagnosticCollector对象
  86. DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
  87.  
  88. //建立一个用于保存java文件管理器ClassFileManage
  89. //每一个文件被保存在JavaClassObject的类里面
  90. ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(diagnosticCollector, null, null));
  91.  
  92. //使用编译选项可以默认编译行为,编译选项是一个String的Iterable集合
  93. List<String> options = new ArrayList<>();
  94. options.add("-encoding");
  95. options.add("UTF-8");
  96. options.add("-classpath");
  97. options.add(this.classpath);
  98.  
  99. //传递源文件,是一个JavaFileObject的集合
  100. List<JavaFileObject> fileObjectList = new ArrayList<>();
  101. fileObjectList.add(new JavaSourceObject(className, javaCode));
  102.  
  103. //创建任务
  104. JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnosticCollector, options, null, fileObjectList);
  105. //执行编译
  106. Boolean call = task.call();
  107. if (call) {
  108. //编译成功
  109. System.out.println("编译成功");
  110. } else {
  111. //编译失败
  112. System.out.println("编译失败");
  113. String error = null;
  114. for (Diagnostic diagnostic : diagnosticCollector.getDiagnostics()) {
  115. error += compilePrint(diagnostic);
  116. }
  117. throw new RuntimeException(error);
  118. }
  119. return fileManager.getJavaClassObject();
  120. }
  121.  
  122. /**
  123. * @param diagnostic
  124. * @return
  125. * @MethodName : compilePrint
  126. * @Description : 输出编译错误信息
  127. */
  128. private String compilePrint(Diagnostic diagnostic) {
  129.  
  130. System.out.println("Code:" + diagnostic.getCode());
  131. System.out.println("Kind:" + diagnostic.getKind());
  132. System.out.println("Position:" + diagnostic.getPosition());
  133. System.out.println("Start Position:" + diagnostic.getStartPosition());
  134. System.out.println("End Position:" + diagnostic.getEndPosition());
  135. System.out.println("Source:" + diagnostic.getSource());
  136. System.out.println("Message:" + diagnostic.getMessage(null));
  137. System.out.println("LineNumber:" + diagnostic.getLineNumber());
  138. System.out.println("ColumnNumber:" + diagnostic.getColumnNumber());
  139. StringBuffer res = new StringBuffer();
  140. res.append("Code:[" + diagnostic.getCode() + "]\n");
  141. res.append("Kind:[" + diagnostic.getKind() + "]\n");
  142. res.append("Position:[" + diagnostic.getPosition() + "]\n");
  143. res.append("Start Position:[" + diagnostic.getStartPosition() + "]\n");
  144. res.append("End Position:[" + diagnostic.getEndPosition() + "]\n");
  145. res.append("Source:[" + diagnostic.getSource() + "]\n");
  146. res.append("Message:[" + diagnostic.getMessage(null) + "]\n");
  147. res.append("LineNumber:[" + diagnostic.getLineNumber() + "]\n");
  148. res.append("ColumnNumber:[" + diagnostic.getColumnNumber() + "]\n");
  149.  
  150. return res.toString();
  151. }
  152.  
  153. /**
  154. * 创建一个新的ClassLoader
  155. *
  156. * @param ruleName
  157. */
  158. public void createNewClassLoader(String ruleName) {
  159. try {
  160. DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this.classLoader);
  161. DataEventFactory.eventClassLoaders.put(ruleName, dynamicClassLoader);
  162. } catch (Exception e) {
  163. e.printStackTrace();
  164. }
  165. }
  166. }
 

JavaClassObject.java

 
  1. package com.compile.shell;
  2.  
  3. import javax.tools.SimpleJavaFileObject;
  4. import java.io.ByteArrayOutputStream;
  5. import java.io.IOException;
  6. import java.io.OutputStream;
  7. import java.net.URI;
  8.  
  9. /**
  10. * @Description: 获取编译后的class文件
  11. * @Auther: wjx
  12. * @Date: 2019/1/18 14:25
  13. */
  14. public class JavaClassObject extends SimpleJavaFileObject {
  15.  
  16. protected final ByteArrayOutputStream bos = new ByteArrayOutputStream();
  17.  
  18. /**
  19. * @param name
  20. * @param kind
  21. */
  22. protected JavaClassObject(String name, Kind kind) {
  23. super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
  24. }
  25.  
  26.  
  27. public byte[] getBytes() {
  28. return bos.toByteArray();
  29. }
  30.  
  31.  
  32. @Override
  33. public OutputStream openOutputStream() throws IOException {
  34. return bos;
  35. }
  36. }
 

JavaSourceObject.java

 
  1. package com.compile.shell;
  2.  
  3. import javax.tools.SimpleJavaFileObject;
  4. import java.io.IOException;
  5. import java.net.URI;
  6.  
  7. /**
  8. * @Description: 传递java源文件
  9. * @Auther: wjx
  10. * @Date: 2019/1/18 14:45
  11. */
  12. public class JavaSourceObject extends SimpleJavaFileObject {
  13.  
  14. public static String packageName = "com.compile.";
  15.  
  16. private String javaCode;
  17.  
  18. /**
  19. * 传入.java文件
  20. */
  21. protected JavaSourceObject(String className, String javaCode) {
  22. super(URI.create("string:///" + className.replace(",", "/") +
  23. Kind.SOURCE.extension), Kind.SOURCE);
  24. this.javaCode = javaCode;
  25. }
  26.  
  27. @Override
  28. public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
  29. return javaCode;
  30. }
  31. }
 

compile-pojo 模块

 

pom.xml

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>springboot-java-compile</artifactId>
  7. <groupId>com.wjx</groupId>
  8. <version>0.0.1-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11.  
  12. <artifactId>compile-pojo</artifactId>
  13.  
  14.  
  15. <dependencies>
  16. <dependency>
  17. <groupId>org.projectlombok</groupId>
  18. <artifactId>lombok</artifactId>
  19. <optional>true</optional>
  20. </dependency>
  21. </dependencies>
  22. </project>
 

CompilePojo.java

 
  1. package com.compile.pojo;
  2.  
  3. import lombok.Data;
  4.  
  5. /**
  6. * @Description: 编译实体类
  7. * @Auther: wjx
  8. * @Date: 2019/1/29 14:52
  9. */
  10. @Data
  11. public class CompilePojo {
  12. /**
  13. * 规则名称
  14. */
  15. private String ruleName;
  16.  
  17. /**
  18. * 包名称
  19. */
  20. private String packageName;
  21.  
  22. /**
  23. * 类名称
  24. */
  25. private String className;
  26.  
  27. /**
  28. * java代码片段
  29. */
  30. private String javaCode;
  31.  
  32. /**
  33. * 方法名
  34. */
  35. private String methodName;
  36. /**
  37. * 参数值
  38. */
  39. public String params;
  40. }
 

compile-dao 模块

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>springboot-java-compile</artifactId>
  7. <groupId>com.wjx</groupId>
  8. <version>0.0.1-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11.  
  12. <artifactId>compile-dao</artifactId>
  13.  
  14.  
  15. <dependencies>
  16.  
  17. <dependency>
  18. <groupId>com.wjx</groupId>
  19. <artifactId>compile-shell</artifactId>
  20. <version>0.0.1-SNAPSHOT</version>
  21. </dependency>
  22.  
  23. <dependency>
  24. <groupId>com.wjx</groupId>
  25. <artifactId>compile-pojo</artifactId>
  26. <version>0.0.1-SNAPSHOT</version>
  27. </dependency>
  28.  
  29. </dependencies>
  30. </project>
 

 

compile-service 模块

pom.xml

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>springboot-java-compile</artifactId>
  7. <groupId>com.wjx</groupId>
  8. <version>0.0.1-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11.  
  12. <artifactId>compile-service</artifactId>
  13.  
  14.  
  15. <dependencies>
  16. <dependency>
  17. <groupId>com.wjx</groupId>
  18. <artifactId>compile-dao</artifactId>
  19. <version>0.0.1-SNAPSHOT</version>
  20. </dependency>
  21. </dependencies>
  22. </project>
 

CompileService.java

 
  1. package com.compile.service;
  2.  
  3. /**
  4. * @Description:
  5. * @Auther: wjx
  6. * @Date: 2019/1/30 10:58
  7. */
  8.  
  9. public interface CompileService {
  10.  
  11. /**
  12. * 执行
  13. *
  14. * @param ruleName
  15. * @param javaCode
  16. * @return
  17. */
  18. Object execute(String ruleName, String javaCode);
  19.  
  20. /**
  21. * 执行方法
  22. *
  23. * @param ruleName
  24. * @param javaCode
  25. * @param methodName
  26. * @param parameter
  27. * @return
  28. */
  29. Object executeMethod(String ruleName, String javaCode, String methodName, Object... parameter);
  30. }
 

CompileServiceImpl.java

 
  1. package com.compile.service.impl;
  2.  
  3. import com.compile.executor.JavaShellExecutor;
  4. import com.compile.service.CompileService;
  5. import com.compile.shell.DynamicEngine;
  6. import com.compile.shell.JavaClassObject;
  7. import org.springframework.stereotype.Service;
  8.  
  9. import java.util.Map;
  10. import java.util.concurrent.ConcurrentHashMap;
  11.  
  12. /**
  13. * @Description:
  14. * @Auther: wjx
  15. * @Date: 2019/1/30 10:59
  16. */
  17. @Service
  18. public class CompileServiceImpl extends JavaShellExecutor implements CompileService {
  19.  
  20. private Map<String, Class<?>> executorMap = new ConcurrentHashMap<>();
  21.  
  22. /**
  23. * 执行
  24. *
  25. * @param ruleName
  26. * @param javaCode
  27. * @return
  28. */
  29. @Override
  30. public Object execute(String ruleName, String javaCode) {
  31. Class<?> aClass = compileGetClass(ruleName, javaCode);
  32. Object o = null;
  33. try {
  34. o = aClass.newInstance();
  35. System.out.println(o);
  36. } catch (Exception e) {
  37. e.printStackTrace();
  38. }
  39. return o;
  40. }
  41.  
  42. /**
  43. * 执行方法
  44. *
  45. * @param ruleName
  46. * @param javaCode
  47. * @param methodName
  48. * @param parameter
  49. * @return
  50. */
  51. @Override
  52. public Object executeMethod(String ruleName, String javaCode, String methodName, Object... parameter) {
  53. if (executorMap.get(ruleName) == null) {
  54. execute(ruleName, javaCode);
  55. }
  56. Class<?> aClass = executorMap.get(ruleName);
  57. try {
  58. return getMethodResult(aClass, methodName, parameter);
  59. } catch (Exception e) {
  60. e.printStackTrace();
  61. }
  62. return null;
  63. }
  64.  
  65. /**
  66. * 编译Java文件
  67. *
  68. * @param ruleName
  69. * @param javaCode
  70. * @return
  71. */
  72. private Class<?> compileGetClass(String ruleName, String javaCode) {
  73. //先清空当前的ruleName的值
  74. executorMap.remove(ruleName);
  75. JavaClassObject classObject = (JavaClassObject) DynamicEngine.getInstance().javaCodeToObject(getClassName(ruleName), javaCode);
  76. //获取编译的结果
  77. Class<?> compileResult = getCompileResult(getClassName(ruleName), classObject, ruleName);
  78. executorMap.put(ruleName, compileResult);
  79. return compileResult;
  80. }
  81.  
  82. }
 

compile-web 模块

pom.xml

 
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>springboot-java-compile</artifactId>
  7. <groupId>com.wjx</groupId>
  8. <version>0.0.1-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11.  
  12. <artifactId>compile-web</artifactId>
  13.  
  14.  
  15. <dependencies>
  16. <dependency>
  17. <groupId>com.wjx</groupId>
  18. <artifactId>compile-service</artifactId>
  19. <version>0.0.1-SNAPSHOT</version>
  20. </dependency>
  21. </dependencies>
  22.  
  23.  
  24. <!--打包-->
  25. <build>
  26. <plugins>
  27. <plugin>
  28. <groupId>org.springframework.boot</groupId>
  29. <artifactId>spring-boot-maven-plugin</artifactId>
  30. <configuration>
  31. <!-- 指定该Main Class为全局的唯一入口 -->
  32. <mainClass>com.Application</mainClass>
  33. <!--<layout>ZIP</layout>-->
  34. </configuration>
  35. <executions>
  36. <execution>
  37. <phase>package</phase>
  38. <goals>
  39. <goal>repackage</goal>
  40. </goals>
  41. </execution>
  42. </executions>
  43. </plugin>
  44. </plugins>
  45. </build>
  46. </project>
 

CompileController.java

 
  1. package com.compile.controller;
  2.  
  3. import com.alibaba.fastjson.JSONObject;
  4. import com.compile.pojo.CompilePojo;
  5. import com.compile.service.CompileService;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.web.bind.annotation.RequestBody;
  8. import org.springframework.web.bind.annotation.RequestMapping;
  9. import org.springframework.web.bind.annotation.RequestMethod;
  10. import org.springframework.web.bind.annotation.RestController;
  11.  
  12. /**
  13. * @Description:
  14. * @Auther: wjx
  15. * @Date: 2019/1/30 11:13
  16. */
  17. @RestController
  18. public class CompileController {
  19.  
  20. @Autowired
  21. private CompileService compileService;
  22.  
  23. /**
  24. * 编译
  25. *
  26. * @param compilePojo
  27. * @return
  28. */
  29. @RequestMapping(value = "execute", method = RequestMethod.POST)
  30. public Object execute(@RequestBody CompilePojo compilePojo) {
  31. JSONObject result = new JSONObject();
  32. try {
  33. Object returnData = compileService.execute(compilePojo.getRuleName(), compilePojo.getJavaCode());
  34. result.put("code", "编译成功,内容是:" + returnData);
  35. } catch (Exception e) {
  36. result.put("code", e.getMessage());
  37. e.printStackTrace();
  38. }
  39. return result;
  40. }
  41.  
  42. /**
  43. * 执行编译获取方法的返回值
  44. *
  45. * @param compilePojo
  46. * @return
  47. */
  48. @RequestMapping(value = "executeMethod", method = RequestMethod.POST)
  49. public Object executeMethod(@RequestBody CompilePojo compilePojo) {
  50. JSONObject result = new JSONObject();
  51. try {
  52. Object returnData = compileService.executeMethod(compilePojo.getRuleName(), compilePojo.getJavaCode(),
  53. compilePojo.getMethodName(), compilePojo.getParams());
  54. result.put("code", "编译成功,内容是:" + returnData);
  55. } catch (Exception e) {
  56. result.put("code", e.getMessage());
  57. e.printStackTrace();
  58. }
  59. return result;
  60. }
  61. }
 

 

测试项目

没有获取方法名

 
  1. {
  2. "ruleName":"TestScripts",
  3. "javaCode":"package com.compile;public class TestScripts { public String hi(String name) { return \" hi, \" + name; } }"
  4. }
 

返回结果

获取方法名

 
  1. {
  2. "ruleName":"TestScripts",
  3. "javaCode":"package com.compile;public class TestScripts { public String hi(String name) { return \" hi, \" + name; } }",
  4. "methodName":"hi",
  5. "params":"wjx"
  6. }
 

返回结果

 

 

打包部署测试

 

idea 右侧

springboot-java-compile 下面的 Lifecycle ,点击 package 打包

运行 jar  

java -jar compile-web-0.0.1-SNAPSHOT.jar

继续测试,

发现报错了,好吧,只能调试程序了,

下面讲述一下 idea 如何调试 jar 包

java -jar -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 [jar 文件名]

会发现我们的项目在 5005 被监听

下面在 idea 添加远程调试 Remote

点击 Edit Configurations 

以此点击左侧 + ,和下面 Remote 默认就 ok 了

项目里面打上断点,启动项目

控制台出现

继续测试,

发现 

ToolProvider.getSystemJavaCompiler()获取的结果是null,查阅资料发现

上网搜了下,直接说就是找不到 jdk lib 目录下 tools.jar 文件,没法编译

查看 JAVA_HOME

解决方法就是 这个目录下面的 lib 文件夹的 tool.jar 拷贝到  jre1.8.0_191 下面就可以运行了

再次测试

到此已经完成了。

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;

/**
 * @author xm
 * @version 1.0
 * @date 2022/7/22 19:45
 */
@Slf4j
public class JavaUils {
    private JavaUils() {
    }

    public static void main(String[] args) throws IOException {
        ByteArrayOutputStream error = JavaUils.compileFile(javaList, compileExtJarNameSet);
        if (error != null) {
            log.error("compileFile fail. -->" + error);
        }
        // 导出jar包
        File file = JavaUils.exportJar("jarName.jar");
        JavaUils.deleteTempFile();
    }

    /**
     * 编译文件生成路径
     */
    private static final String COMPILE_JAVA_FILE_PATH = "." + File.separator + "compile_java_file" + File.separator;
    private static final String COMPILE_LIB = "compile_lib/";
    private static final JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
    public static final String JAVA_SUFFIX = ".java";

    static {
        log.info("---------javaCompiler------" + javaCompiler);
        if (javaCompiler == null) {
            // 本地cmd运行时ToolProvider.getSystemJavaCompiler()会获取不到javaCompiler,可以使用JavacTool.create()来创建
            log.error("ToolProvider.getSystemJavaCompiler() is null.");
        }
    }

    public static String packagePath(String fullPathClassName) {
        if (fullPathClassName.endsWith(JAVA_SUFFIX)) {
            return fullPathClassName.substring(0, fullPathClassName.lastIndexOf("."))
                    .substring(0, fullPathClassName.lastIndexOf("."))
                    .replace(".", File.separator) + File.separator;
        }
        return fullPathClassName.substring(0, fullPathClassName.lastIndexOf("."))
                .replace(".", File.separator) + File.separator;
    }

    /**
     * @param fullPathClassName com..dto.order.TestReq
     * @return
     */
    public static String fileName(String fullPathClassName) {
        if (fullPathClassName.endsWith(JAVA_SUFFIX)) {
            return fullPathClassName.substring(0, fullPathClassName.length() - JAVA_SUFFIX.length())
                    .substring(fullPathClassName.lastIndexOf(".") + 1) + JAVA_SUFFIX;
        }
        return fullPathClassName.substring(fullPathClassName.lastIndexOf(".") + 1) + JAVA_SUFFIX;
    }

    public static void validatedCompileFile(String fullPathClassName, String javaStr) {
        int lastIndex = fullPathClassName.lastIndexOf(".");
        if (lastIndex > 0) {
            String packagePath = fullPathClassName.substring(0, lastIndex);
            if (!javaStr.contains(packagePath)) {
                throw new CompilationException("validated packagePath fail");
            }
            String className = fullPathClassName.substring(lastIndex + 1);
            if (!javaStr.contains(className)) {
                throw new CompilationException("validated className fail");
            }
        }
    }

    /**
     * 编译指定的所有.java文件,编译后的文件
     * @param collect 需要包含待编译的类文件.java及类的全路径类名
     * @param copyJarNameSet 编译需要用到的第三方依赖jar。会去到springboot打包时默认的lib下获取,如需手动指定需要调整代码
     * @return 为null时表示成功;非null时包含编译出错时的错误信息,可直接转为字符串打印
     */
    public static ByteArrayOutputStream compileFile(Collection<JavaCompilerInfo> collect, Set<String> copyJarNameSet) throws IOException {
        String loadJarParam = getLoadJarParam(copyJarNameSet);
        String[] strings;
        int i = 2;
        if (loadJarParam != null && !loadJarParam.isEmpty()) {
            strings = new String[collect.size() + 4];
            strings[2] = "-classpath";
            strings[3] = loadJarParam;
            i = 4;
        }else {
            strings = new String[collect.size() + 2];
        }
        // 设置编码,避免编码问题
        strings[0] = "-encoding";
        strings[1] = "utf-8";
        for (JavaCompilerInfo info : collect) {
            File file = new File(COMPILE_JAVA_FILE_PATH + JavaUils.packagePath(info.getFullPathClassName()));
            if (!file.exists()) {
                file.mkdirs();
            }
            file = new File(file.getPath() + File.separator + JavaUils.fileName(info.getFullPathClassName()));
            file.createNewFile();
            FileUtils.write(file, info.getJavaStr(), StandardCharsets.UTF_8);
            strings[i] = file.getPath();
            i++;
        }
        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
        int result = javaCompiler.run(null, null, byteOutputStream, strings);
        if (result == 0) {
            return null;
        }
        return byteOutputStream;
    }

    private static String getLoadJarParam(Set<String> copyJarNameSet) throws IOException {
        if (copyJarNameSet == null || copyJarNameSet.isEmpty()) {
            return null;
        }
        JarFile jarFile = getJarFile();
        if (jarFile == null) {
            return null;
        }
        StringBuilder builder = new StringBuilder();
        for (String jarName : copyJarNameSet) {
            if (jarName.trim().isEmpty()) {
                continue;
            }
            String fileAbsolutePath = copyJarToCompileLib(jarFile, jarName);
            if (fileAbsolutePath != null) {
                builder.append(fileAbsolutePath);
            }
        }
        String optionStr = builder.toString();
        log.info("compiler option -classpath:{}", optionStr);
        return optionStr;
    }

    private static String copyJarToCompileLib(JarFile jarFile , String jarName) {
        try {
            File destination = new File(COMPILE_LIB + jarName);
            if (destination.exists() && destination.length() > 0) {
                log.info("copyJarToCompileLib the jar file already exists. file.length():" + destination.length());
                return destination.getAbsolutePath() + File.pathSeparator;
            }
            JarEntry jarEntry = jarFile.getJarEntry("BOOT-INF/lib/" + jarName);
            if (jarEntry == null) {
                log.error("jar file not found. jarName:{}", jarName);
                return null;
            }
            InputStream inputStream = jarFile.getInputStream(jarEntry);
            FileUtils.copyInputStreamToFile(inputStream, destination);
            log.info("copyJarToCompileLib success. jarName:{}", jarName);
            return destination.getAbsolutePath() + File.pathSeparator;
        } catch (Exception e) {
            log.error("copyJarToCompileLib fail. jarName:{}", jarName, e);
        }
        return null;
    }

    private static JarFile getJarFile() throws IOException {
        String projectJarName = "springboot-test.jar";
        File file = new File(projectJarName);
        if (file.exists()) {
            return new JarFile(file);
        }
        File file2 = new File("lib" + File.separator + projectJarName);
        if (file2.exists()) {
            return new JarFile(file2);
        }
        log.error("projectJar:{} not found", projectJarName);
        return null;
    }

    public static boolean compileFile(String javaStr) {
        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
        int result = javaCompiler.run(null, null, byteOutputStream, javaStr);
        if (result == 0) {
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }
    private static File getOutFile(String outPath) throws IOException {
        File file = new File(outPath);
        if (!file.exists()) {
            file.createNewFile();
        }
        return file;
    }

    public static File exportJar(String outPath) throws IOException {
        return exportJar(COMPILE_JAVA_FILE_PATH, outPath);
    }

    public static File exportJar(String packagePath, String outPath) throws IOException {
        File outFile = getOutFile(outPath);
        JarOutputStream out = new JarOutputStream(new FileOutputStream(outFile), getManifest());
        createTempJarInner(out, new File(packagePath), "");
        out.flush();
        out.close();
        return outFile;
    }

    /**
     * @param out
     * @param f    绝对路径 G:\git\compile\xm-compile\src\test\java\com\dto\OrderDto.java
     * @param base 导出jar包里面此文件的路径
     * @throws IOException
     */
    private static void createTempJarInner(JarOutputStream out, File f, String base) throws IOException {
        if (f.isDirectory()) {
            File[] fl = f.listFiles();
            if (fl != null && fl.length > 0) {
                if (base.length() > 0) {
                    base = base + "/";
                }
                for (File file : fl) {
                    createTempJarInner(out, file, base + file.getName());
                }
            }
        } else {
            if (base.endsWith(".class")) {
                toJar(out, f, base);
            }
        }
    }

    public static void deleteTempFile() throws IOException {
        FileUtils.forceDelete(new File(COMPILE_JAVA_FILE_PATH));
    }

    private static void toJar(JarOutputStream out, File f, String base) throws IOException {
        log.info("export to jar -->" + base);
        out.putNextEntry(new JarEntry(base));
        try(FileInputStream in = new FileInputStream(f)) {
            byte[] buffer = new byte[1024];
            int n = in.read(buffer);
            while (n != -1) {
                out.write(buffer, 0, n);
                n = in.read(buffer);
            }
        }
    }

    private static Manifest getManifest() {
        Manifest manifest = new Manifest();
        manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
        //manifest.getMainAttributes().putValue("Main-Class", "Show");//指定Main Class
        return manifest;
    }
}
 
  1.  
  2. import java.io.BufferedReader;
  3. import java.io.InputStream;
  4. import java.io.InputStreamReader;
  5.  
  6. import javax.tools.JavaCompiler;
  7. import javax.tools.ToolProvider;
  8.  
  9. import org.junit.Test;
  10. import org.junit.runner.RunWith;
  11. import org.springframework.boot.test.context.SpringBootTest;
  12. import org.springframework.context.annotation.ComponentScan;
  13. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
  14.  
  15. import com.piao.main.ApplicationAdmin;
  16.  
  17.  
  18. @RunWith(SpringJUnit4ClassRunner.class)
  19. @ComponentScan("com.piao")
  20. @SpringBootTest(classes = ApplicationAdmin.class)
  21. public class Compiler {
  22.  
  23. @Test
  24. public void compiler1(){
  25. String javaAbsolutePath = "D:/test/AlTest1.java";
  26. JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
  27. int status = compiler.run(null, null, null, "-encoding", "UTF-8", "-classpath", javaAbsolutePath.toString(), javaAbsolutePath);
  28. if(status!=0){
  29. System.out.println("没有编译成功!");
  30. }
  31.  
  32. }
  33. @Test
  34. public void compiler2(){
  35. String javaAbsolutePath = "D:/test/AlTest2.java";
  36.  
  37. try {
  38. Process process = Runtime.getRuntime().exec("javac -classpath D:/test/ " + javaAbsolutePath);
  39. InputStream errorStream = process.getErrorStream();
  40. InputStreamReader inputStreamReader = new InputStreamReader(errorStream);
  41. BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
  42. String line = null;
  43. while ((line=bufferedReader.readLine()) != null){
  44. System.out.println(line);
  45. }
  46. int exitVal = process.waitFor();
  47. System.out.println("Process exitValue: " + exitVal);
  48. } catch (Exception e) {
  49. e.printStackTrace();
  50. }
  51. }
  52.  
  53.  
  54. }
 

运行后, D:\test 目录会生成两个 class 文件

 

其中方法 compiler1 是使用 jdk 自带的 rt.jar 中的 javax.tools 包提供的编译器

方法 compiler2 是使用 Runtime 执行 javac 命令

 

posted @ 2024-07-01 09:34  CharyGao  阅读(42)  评论(0)    收藏  举报