1.项目结构
各模块介绍
compile-dao:dao层,数据库持久化层,本文暂时用不到
compile-pojo:实体类
compile-service:业务逻辑模块
compile-shell:Java动态编译的一些java文件
compile-web:SpringBoot的入口
springboot-java-compile父工程pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<modules>
<module>compile-shell</module>
<module>compile-service</module>
<module>compile-web</module>
<module>compile-dao</module>
<module>compile-pojo</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wjx</groupId>
<artifactId>springboot-java-compile</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-java-compile</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<!--指定使用maven打包-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<skipTests>true</skipTests> <!--默认关掉单元测试 -->
</configuration>
</plugin>
</plugins>
</build>
</project>
compile-shell 模块
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot-java-compile</artifactId>
<groupId>com.wjx</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>compile-shell</artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
JavaShellExecutor.java
package com.compile.executor;
import com.compile.shell.*;
import lombok.Data;
import java.lang.reflect.Method;
/**
* @Description: 获取编译后的结果
* @Auther: wjx
* @Date: 2019/1/29 15:45
*/
@Data
public class JavaShellExecutor {
/**
* 获取类名,className等于ruleName
*
* @param ruleName
* @return
*/
public String getClassName(String ruleName) {
String className = ruleName.replaceAll("[^a-z^A-Z]", "");
return className.substring(0, 1).toUpperCase() + className.substring(1);
}
/**
* 获取编译的结果
*
* @param className
* @param classObject
* @param ruleName
* @return
*/
public Class<?> getCompileResult(String className, JavaClassObject classObject, String ruleName) {
//使用新的自定义Classloader,记得每个规则使用一个新的classloader,
//当规则更新时,老的classloader可被释放
try {
return getLoadClass(className, classObject, ruleName);
} catch (Throwable e) {
throw new RuntimeException("load class error!" + ",className=" + className + "\r\n" + e.getMessage(), e);
}
}
/**
* 获取加载的Class
*
* @param className
* @param classObject
* @param ruleName
* @return
*/
public Class<?> getLoadClass(String className, JavaClassObject classObject, String ruleName) {
if (DataEventFactory.eventClassLoaders.get(ruleName) == null) {
DynamicEngine.getInstance().createNewClassLoader(ruleName);
}
DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(DataEventFactory.eventClassLoaders.get(ruleName));
Class aClass = dynamicClassLoader.loadClass(JavaSourceObject.packageName + ruleName, classObject);
return aClass;
}
/**
* 执行方法并返回结果
*
* @param aClass
* @param methodName
* @return
*/
public Object getMethodResult(Class aClass, String methodName, Object... params) {
Object result = null;
try {
Object instance = aClass.newInstance();
Method method = aClass.getMethod(methodName, String.class);
result = method.invoke(instance, params);
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
ClassFileManager.java
package com.compile.shell;
import javax.tools.*;
import java.io.IOException;
/**
* @Description: 类文件管理器
* @Auther: wjx
* @Date: 2019/1/18 14:22
*/
public class ClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
private JavaClassObject javaClassObject;
protected ClassFileManager(StandardJavaFileManager standardJavaFileManager) {
super(standardJavaFileManager);
}
public JavaClassObject getJavaClassObject() {
return javaClassObject;
}
/**
* 源文件被编译成 .class 文件
*
* @param location
* @param className
* @param kind
* @param sibling
* @return
* @throws IOException
*/
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
javaClassObject = new JavaClassObject(className, kind);
return javaClassObject;
}
}
DataEventFactory.java
package com.compile.shell;
import java.net.URLClassLoader;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Description: 创建一个存放ClassLoad的类
* @Auther: wjx
* @Date: 2019/1/18 15:47
*/
public class DataEventFactory {
/**
* 存放当前的ClassLoader
*/
public static Map<String, URLClassLoader> eventClassLoaders = new ConcurrentHashMap<>();
}
DynamicClassLoader.java
package com.compile.shell;
import java.net.URL;
import java.net.URLClassLoader;
/**
* @Description: 读取java文件编译结果
* @Auther: wjx
* @Date: 2019/1/18 15:41
*/
public class DynamicClassLoader extends URLClassLoader {
public DynamicClassLoader(ClassLoader parent) {
super(new URL[0], parent);
}
public Class findClassByName(String className) throws ClassNotFoundException {
return this.findClass(className);
}
/**
* 加载读取返回的类
* @param fullName fullName = packageName + className
* @param classObject
* @return
*/
public Class loadClass(String fullName, JavaClassObject classObject) {
byte[] bytes = classObject.getBytes();
return this.defineClass(fullName, bytes, 0, bytes.length);
}
}
DynamicEngine.java核心类
package com.compile.shell;
import com.compile.executor.JavaShellExecutor;
import javax.tools.*;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
/**
* Compile动态执行java的String代码到内存中并执行
* 1.创建 URLClassLoader 类加载器
* 2.获取当前执行的classpath的所有jar包的路径
* 3.通过java的ToolProvider创建JavaCompile,用来执行class源文件
* 4.创建DiagnosticCollector用来执行获取执行失败的错误结果
* 5.添加动态执行的编译环境 options 是个集合,添加内容,字符集,classpath等
* 6.传入JavaFileObject的java文件,是个集合,创建JavaSourceObject实现这个接口,Kind.SOURCE.extension = '.java'
* 7.创建任务并执行
* 8.获取执行完成后的返回JavaClassObject类
* 9.创建DynamicClassLoader来加载类 ,defineClass这个方法
*
* @Description:
* @Auther: wjx
* @Date: 2019/1/18 14:55
*/
public class DynamicEngine extends JavaShellExecutor {
private static DynamicEngine dynamicEngine;
/**
* 单例模式
*
* @return
*/
public static DynamicEngine getInstance() {
if (dynamicEngine == null) {
synchronized (DynamicEngine.class) {
if (dynamicEngine == null) {
dynamicEngine = new DynamicEngine();
}
}
}
return dynamicEngine;
}
//创建动态加载jar包
private URLClassLoader classLoader;
//当前的classpath环境
private String classpath;
private DynamicEngine() {
//获取类加载器
this.classLoader = (URLClassLoader) this.getClass().getClassLoader();
this.buildClasspath();
}
private void buildClasspath() {
//初始化classpath为null
this.classpath = null;
StringBuilder sb = new StringBuilder();
for (URL url : this.classLoader.getURLs()) {
String f = url.getFile();
sb.append(f).append(File.pathSeparator);
}
this.classpath = sb.toString();
}
/**
* 编译
*
* @param className
* @param javaCode
* @return
*/
public Object javaCodeToObject(String className, String javaCode) {
//记录编译起始时间
long startTime = System.currentTimeMillis();
//通过java工具获取编译的compile
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//创建DiagnosticCollector对象
DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
//建立一个用于保存java文件管理器ClassFileManage
//每一个文件被保存在JavaClassObject的类里面
ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(diagnosticCollector, null, null));
//使用编译选项可以默认编译行为,编译选项是一个String的Iterable集合
List<String> options = new ArrayList<>();
options.add("-encoding");
options.add("UTF-8");
options.add("-classpath");
options.add(this.classpath);
//传递源文件,是一个JavaFileObject的集合
List<JavaFileObject> fileObjectList = new ArrayList<>();
fileObjectList.add(new JavaSourceObject(className, javaCode));
//创建任务
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnosticCollector, options, null, fileObjectList);
//执行编译
Boolean call = task.call();
if (call) {
//编译成功
System.out.println("编译成功");
} else {
//编译失败
System.out.println("编译失败");
String error = null;
for (Diagnostic diagnostic : diagnosticCollector.getDiagnostics()) {
error += compilePrint(diagnostic);
}
throw new RuntimeException(error);
}
return fileManager.getJavaClassObject();
}
/**
* @param diagnostic
* @return
* @MethodName : compilePrint
* @Description : 输出编译错误信息
*/
private String compilePrint(Diagnostic diagnostic) {
System.out.println("Code:" + diagnostic.getCode());
System.out.println("Kind:" + diagnostic.getKind());
System.out.println("Position:" + diagnostic.getPosition());
System.out.println("Start Position:" + diagnostic.getStartPosition());
System.out.println("End Position:" + diagnostic.getEndPosition());
System.out.println("Source:" + diagnostic.getSource());
System.out.println("Message:" + diagnostic.getMessage(null));
System.out.println("LineNumber:" + diagnostic.getLineNumber());
System.out.println("ColumnNumber:" + diagnostic.getColumnNumber());
StringBuffer res = new StringBuffer();
res.append("Code:[" + diagnostic.getCode() + "]\n");
res.append("Kind:[" + diagnostic.getKind() + "]\n");
res.append("Position:[" + diagnostic.getPosition() + "]\n");
res.append("Start Position:[" + diagnostic.getStartPosition() + "]\n");
res.append("End Position:[" + diagnostic.getEndPosition() + "]\n");
res.append("Source:[" + diagnostic.getSource() + "]\n");
res.append("Message:[" + diagnostic.getMessage(null) + "]\n");
res.append("LineNumber:[" + diagnostic.getLineNumber() + "]\n");
res.append("ColumnNumber:[" + diagnostic.getColumnNumber() + "]\n");
return res.toString();
}
/**
* 创建一个新的ClassLoader
*
* @param ruleName
*/
public void createNewClassLoader(String ruleName) {
try {
DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this.classLoader);
DataEventFactory.eventClassLoaders.put(ruleName, dynamicClassLoader);
} catch (Exception e) {
e.printStackTrace();
}
}
}
JavaClassObject.java
package com.compile.shell;
import javax.tools.SimpleJavaFileObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
/**
* @Description: 获取编译后的class文件
* @Auther: wjx
* @Date: 2019/1/18 14:25
*/
public class JavaClassObject extends SimpleJavaFileObject {
protected final ByteArrayOutputStream bos = new ByteArrayOutputStream();
/**
* @param name
* @param kind
*/
protected JavaClassObject(String name, Kind kind) {
super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
}
public byte[] getBytes() {
return bos.toByteArray();
}
@Override
public OutputStream openOutputStream() throws IOException {
return bos;
}
}
JavaSourceObject.java
package com.compile.shell;
import javax.tools.SimpleJavaFileObject;
import java.io.IOException;
import java.net.URI;
/**
* @Description: 传递java源文件
* @Auther: wjx
* @Date: 2019/1/18 14:45
*/
public class JavaSourceObject extends SimpleJavaFileObject {
public static String packageName = "com.compile.";
private String javaCode;
/**
* 传入.java文件
*/
protected JavaSourceObject(String className, String javaCode) {
super(URI.create("string:///" + className.replace(",", "/") +
Kind.SOURCE.extension), Kind.SOURCE);
this.javaCode = javaCode;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return javaCode;
}
}
compile-pojo模块
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot-java-compile</artifactId>
<groupId>com.wjx</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>compile-pojo</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
CompilePojo.java
package com.compile.pojo;
import lombok.Data;
/**
* @Description: 编译实体类
* @Auther: wjx
* @Date: 2019/1/29 14:52
*/
@Data
public class CompilePojo {
/**
* 规则名称
*/
private String ruleName;
/**
* 包名称
*/
private String packageName;
/**
* 类名称
*/
private String className;
/**
* java代码片段
*/
private String javaCode;
/**
* 方法名
*/
private String methodName;
/**
* 参数值
*/
public String params;
}
compile-dao模块
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot-java-compile</artifactId>
<groupId>com.wjx</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>compile-dao</artifactId>
<dependencies>
<dependency>
<groupId>com.wjx</groupId>
<artifactId>compile-shell</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.wjx</groupId>
<artifactId>compile-pojo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
compile-service模块
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot-java-compile</artifactId>
<groupId>com.wjx</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>compile-service</artifactId>
<dependencies>
<dependency>
<groupId>com.wjx</groupId>
<artifactId>compile-dao</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
CompileService.java
package com.compile.service;
/**
* @Description:
* @Auther: wjx
* @Date: 2019/1/30 10:58
*/
public interface CompileService {
/**
* 执行
*
* @param ruleName
* @param javaCode
* @return
*/
Object execute(String ruleName, String javaCode);
/**
* 执行方法
*
* @param ruleName
* @param javaCode
* @param methodName
* @param parameter
* @return
*/
Object executeMethod(String ruleName, String javaCode, String methodName, Object... parameter);
}
CompileServiceImpl.java
package com.compile.service.impl;
import com.compile.executor.JavaShellExecutor;
import com.compile.service.CompileService;
import com.compile.shell.DynamicEngine;
import com.compile.shell.JavaClassObject;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Description:
* @Auther: wjx
* @Date: 2019/1/30 10:59
*/
@Service
public class CompileServiceImpl extends JavaShellExecutor implements CompileService {
private Map<String, Class<?>> executorMap = new ConcurrentHashMap<>();
/**
* 执行
*
* @param ruleName
* @param javaCode
* @return
*/
@Override
public Object execute(String ruleName, String javaCode) {
Class<?> aClass = compileGetClass(ruleName, javaCode);
Object o = null;
try {
o = aClass.newInstance();
System.out.println(o);
} catch (Exception e) {
e.printStackTrace();
}
return o;
}
/**
* 执行方法
*
* @param ruleName
* @param javaCode
* @param methodName
* @param parameter
* @return
*/
@Override
public Object executeMethod(String ruleName, String javaCode, String methodName, Object... parameter) {
if (executorMap.get(ruleName) == null) {
execute(ruleName, javaCode);
}
Class<?> aClass = executorMap.get(ruleName);
try {
return getMethodResult(aClass, methodName, parameter);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 编译Java文件
*
* @param ruleName
* @param javaCode
* @return
*/
private Class<?> compileGetClass(String ruleName, String javaCode) {
//先清空当前的ruleName的值
executorMap.remove(ruleName);
JavaClassObject classObject = (JavaClassObject) DynamicEngine.getInstance().javaCodeToObject(getClassName(ruleName), javaCode);
//获取编译的结果
Class<?> compileResult = getCompileResult(getClassName(ruleName), classObject, ruleName);
executorMap.put(ruleName, compileResult);
return compileResult;
}
}
compile-web 模块
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot-java-compile</artifactId>
<groupId>com.wjx</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>compile-web</artifactId>
<dependencies>
<dependency>
<groupId>com.wjx</groupId>
<artifactId>compile-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!--打包-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- 指定该Main Class为全局的唯一入口 -->
<mainClass>com.Application</mainClass>
<!--<layout>ZIP</layout>-->
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
CompileController.java
package com.compile.controller;
import com.alibaba.fastjson.JSONObject;
import com.compile.pojo.CompilePojo;
import com.compile.service.CompileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description:
* @Auther: wjx
* @Date: 2019/1/30 11:13
*/
@RestController
public class CompileController {
@Autowired
private CompileService compileService;
/**
* 编译
*
* @param compilePojo
* @return
*/
@RequestMapping(value = "execute", method = RequestMethod.POST)
public Object execute(@RequestBody CompilePojo compilePojo) {
JSONObject result = new JSONObject();
try {
Object returnData = compileService.execute(compilePojo.getRuleName(), compilePojo.getJavaCode());
result.put("code", "编译成功,内容是:" + returnData);
} catch (Exception e) {
result.put("code", e.getMessage());
e.printStackTrace();
}
return result;
}
/**
* 执行编译获取方法的返回值
*
* @param compilePojo
* @return
*/
@RequestMapping(value = "executeMethod", method = RequestMethod.POST)
public Object executeMethod(@RequestBody CompilePojo compilePojo) {
JSONObject result = new JSONObject();
try {
Object returnData = compileService.executeMethod(compilePojo.getRuleName(), compilePojo.getJavaCode(),
compilePojo.getMethodName(), compilePojo.getParams());
result.put("code", "编译成功,内容是:" + returnData);
} catch (Exception e) {
result.put("code", e.getMessage());
e.printStackTrace();
}
return result;
}
}
测试项目
没有获取方法名
{
"ruleName":"TestScripts",
"javaCode":"package com.compile;public class TestScripts { public String hi(String name) { return \" hi, \" + name; } }"
}
返回结果
获取方法名
{
"ruleName":"TestScripts",
"javaCode":"package com.compile;public class TestScripts { public String hi(String name) { return \" hi, \" + name; } }",
"methodName":"hi",
"params":"wjx"
}
返回结果
打包部署测试
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 下面就可以运行了
再次测试
到此已经完成了。