boot jar 동적컴파일 대응

This commit is contained in:
yunjy
2022-08-22 12:16:58 +09:00
parent 1c94e0c81c
commit 639958fbf7
4 changed files with 91 additions and 19 deletions
+1
View File
@@ -22,6 +22,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect' implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
// implementation 'com.github.darrachequesne:spring-data-jpa-datatables:5.1.0' // implementation 'com.github.darrachequesne:spring-data-jpa-datatables:5.1.0'
// implementation 'com.google.jimfs:jimfs:1.2'
implementation 'org.apache.commons:commons-lang3:3.10' implementation 'org.apache.commons:commons-lang3:3.10'
implementation 'commons-io:commons-io:2.6' implementation 'commons-io:commons-io:2.6'
@@ -2,12 +2,16 @@ package com.eactive.httpmockserver;
import com.eactive.httpmockserver.script.ScriptInfoService; import com.eactive.httpmockserver.script.ScriptInfoService;
import com.eactive.httpmockserver.script.ScriptLoader; import com.eactive.httpmockserver.script.ScriptLoader;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;
@Component @Component
public class PostConstructBean { public class PostConstructBean {
@@ -17,7 +21,7 @@ public class PostConstructBean {
public ScriptInfoService scriptInfoService; public ScriptInfoService scriptInfoService;
@PostConstruct @PostConstruct
public void init() { public void init() throws URISyntaxException {
logger.info("init scripts"); logger.info("init scripts");
try { try {
ScriptLoader.getInstance().init(scriptInfoService); ScriptLoader.getInstance().init(scriptInfoService);
@@ -1,21 +1,21 @@
package com.eactive.httpmockserver.script; package com.eactive.httpmockserver.script;
import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils;
import org.mdkt.compiler.CompilationException;
import org.mdkt.compiler.CompiledCode;
import org.mdkt.compiler.DynamicClassLoader;
import org.mdkt.compiler.InMemoryJavaCompiler; import org.mdkt.compiler.InMemoryJavaCompiler;
import org.springframework.beans.factory.annotation.Autowired;
import javax.tools.JavaCompiler; import javax.tools.*;
import javax.tools.ToolProvider; import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ScriptLoader{ public class ScriptLoader{
String path = ScriptLoader.class.getProtectionDomain().getCodeSource().getLocation().getPath(); String path = ScriptLoader.class.getProtectionDomain().getCodeSource().getLocation().getPath();
@@ -24,9 +24,63 @@ public class ScriptLoader{
private static final ScriptLoader instance = new ScriptLoader(); private static final ScriptLoader instance = new ScriptLoader();
private Map<String, Class<Script>> loadedClass; private Map<String, Class<Script>> loadedClass;
private String classPath = null;
/**
* BOOT 일 경우 JAR 내의 Class 목록이 BOOT-INF로 들어가서 컴파일시 참조가 안되는 문제가 있음
* 임시로 /tmp 폴더에 압축해제하여 해당 경로를 참조하게 하였으나 메모리 내에서 참조가능거나 jar파일을 직접 참조 할 수 있는 방안을 찾아볼 예정임
* Groovy 스크립트로 변경도 검토 중
*/
private ScriptLoader(){ private ScriptLoader(){
this.loadedClass = new HashMap<>(); this.loadedClass = new HashMap<>();
URL url = getClass().getProtectionDomain().getCodeSource().getLocation();
try {
String uri = url.toURI().toString();
System.out.println(uri);
if(StringUtils.contains(uri, "jar:file:" )) {
String jarPath = StringUtils.substringBetween(uri, "jar:file:/", ".jar!") + ".jar";
System.out.println("jarPath => " + jarPath);
if(StringUtils.contains(uri, "BOOT-INF" )) {
String memoryWorkingPath = "/tmp/httpmockworking";
File workingDir = new File(memoryWorkingPath);
workingDir.mkdirs();
java.util.jar.JarFile jar = new java.util.jar.JarFile(jarPath);
java.util.Enumeration enumEntries = jar.entries();
while (enumEntries.hasMoreElements()) {
java.util.jar.JarEntry file = (java.util.jar.JarEntry) enumEntries.nextElement();
java.io.File f = new java.io.File(memoryWorkingPath + "/" + file.getName());
if (file.isDirectory()) { // if its a directory, create it
f.mkdirs();
continue;
}
java.io.InputStream is = jar.getInputStream(file); // get the input stream
BufferedInputStream bis = new BufferedInputStream(is);
java.io.FileOutputStream fos = new java.io.FileOutputStream(f);
BufferedOutputStream bos = new BufferedOutputStream(fos);
int numBytes;
byte[] buffer = new byte[1024];
while ((numBytes = bis.read(buffer))!= -1)
{
bos.write(buffer, 0, numBytes);
}
bis.close();
bos.close();
}
jar.close();
classPath = memoryWorkingPath+"/BOOT-INF/classes;"+memoryWorkingPath+"/BOOT-INF/lib/*;";
}else {
classPath = jarPath;
}
}
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} }
public static ScriptLoader getInstance(){ public static ScriptLoader getInstance(){
@@ -39,12 +93,18 @@ public class ScriptLoader{
return newInstance; return newInstance;
} }
// class DynamicLoader extends ClassLoader{ class DynamicLoader extends ClassLoader{
// public Class<Script> loadScript(String fullClassName, byte[] classData){ public DynamicLoader(){}
// return (Class<Script>) defineClass(fullClassName,
// classData, 0, classData.length); public DynamicLoader(ClassLoader parent){
// } super(parent);
// } }
public Class<Script> loadScript(String fullClassName, byte[] classData){
return (Class<Script>) defineClass(fullClassName,
classData, 0, classData.length);
}
}
public void init(ScriptInfoService scriptInfoService) throws Exception { public void init(ScriptInfoService scriptInfoService) throws Exception {
List<ScriptInfo> scriptInfoList = scriptInfoService.findAllScriptInfos(); List<ScriptInfo> scriptInfoList = scriptInfoService.findAllScriptInfos();
@@ -56,15 +116,21 @@ public class ScriptLoader{
public void load(String scriptName, String scriptCode) throws Exception { public void load(String scriptName, String scriptCode) throws Exception {
String source = scriptCode; String source = scriptCode;
String fullPackage = DYNAMIC_BASE_PACKAGE + "." + scriptName; String fullPackage = DYNAMIC_BASE_PACKAGE + "." + scriptName;
InMemoryJavaCompiler compiler = InMemoryJavaCompiler.newInstance();
compiler.useParentClassLoader(getClass().getClassLoader());
if(StringUtils.isNotBlank(classPath)) {
compiler.useOptions("-classpath", classPath);
}
Class<Script> cls = (Class<Script>) InMemoryJavaCompiler.newInstance().compile(fullPackage, source); Class<Script> cls = (Class<Script>) compiler.compile(fullPackage, source);
// Script newInstance = cls.getDeclaredConstructor().newInstance();
// String xx = newInstance.postProcess("abc","ddd"); // String xx = newInstance.postProcess("abc","ddd");
// System.out.println(xx); // System.out.println(xx);
this.loadedClass.put(scriptName, cls); this.loadedClass.put(scriptName, cls);
} }
public void unLoad(String scriptName) throws ClassNotFoundException, IOException { public void unLoad(String scriptName) throws ClassNotFoundException, IOException {
new File(path + DYNAMIC_BASE_PATH + "/" + scriptName +".class").delete(); new File(path + DYNAMIC_BASE_PATH + "/" + scriptName +".class").delete();
this.loadedClass.remove(scriptName); this.loadedClass.remove(scriptName);
@@ -160,6 +160,7 @@
<label class="col-sm-3 col-form-label">스크립트 명</label> <label class="col-sm-3 col-form-label">스크립트 명</label>
<div class="col-sm-9"> <div class="col-sm-9">
<select class="form-control select2bs4" th:field="*{scriptName}" th:value="*{scriptName}"> style="width: 100%;" > <select class="form-control select2bs4" th:field="*{scriptName}" th:value="*{scriptName}"> style="width: 100%;" >
<option value="">선택안함</option>
<option th:each="scriptInfo : ${scriptInfos}" th:value="${scriptInfo.scriptName}" th:text="${scriptInfo.scriptName + '(' + scriptInfo.scriptDesc + ')'}"></option> <option th:each="scriptInfo : ${scriptInfos}" th:value="${scriptInfo.scriptName}" th:text="${scriptInfo.scriptName + '(' + scriptInfo.scriptDesc + ')'}"></option>
</select> </select>
</div> </div>