perf : 引擎优化
This commit is contained in:
@@ -13,8 +13,8 @@ import org.springframework.context.annotation.Bean;
|
||||
public class JavaCodeAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public JavaCodeEngine javaCodeEngine(JavaCodeProperties javaCodeProperties){
|
||||
return new JavaCodeEngine(javaCodeProperties);
|
||||
public JavaCodeEngine javaCodeEngine(JavaCodeProperties javaCodeProperties) {
|
||||
return new JavaCodeEngine(javaCodeProperties.getClassPath());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -5,19 +5,22 @@ import cn.fateverse.common.core.exception.CustomException;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* 控制台输出捕获
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2024/4/22 17:08
|
||||
*/
|
||||
public class MultiThreadedCapture {
|
||||
|
||||
|
||||
private final static ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||
public class ConsoleCapture {
|
||||
|
||||
|
||||
/**
|
||||
* 捕获方法
|
||||
*
|
||||
* @param task 任務
|
||||
* @return 返回结果
|
||||
*/
|
||||
public static EngineResult capture(Task task) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
PrintStream oldOut = System.out;
|
||||
@@ -27,7 +30,7 @@ public class MultiThreadedCapture {
|
||||
try {
|
||||
result = task.execute();
|
||||
} catch (Exception e) {
|
||||
if (e instanceof CustomException){
|
||||
if (e instanceof CustomException) {
|
||||
throw (CustomException) e;
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
@@ -1,17 +1,16 @@
|
||||
package cn.fateverse.common.code.engine;
|
||||
|
||||
|
||||
import cn.fateverse.common.code.config.JavaCodeProperties;
|
||||
import cn.fateverse.common.code.console.MultiThreadedCapture;
|
||||
import cn.fateverse.common.code.console.ConsoleCapture;
|
||||
import cn.fateverse.common.code.exception.SandboxClassNotFoundException;
|
||||
import cn.fateverse.common.code.lock.SegmentLock;
|
||||
import cn.fateverse.common.code.model.EngineResult;
|
||||
import cn.fateverse.common.code.sandbox.SandboxClassLoader;
|
||||
import cn.fateverse.common.code.sandbox.SandboxSecurityManager;
|
||||
import cn.fateverse.common.core.exception.CustomException;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import javax.tools.JavaCompiler;
|
||||
@@ -39,9 +38,7 @@ public class JavaCodeEngine {
|
||||
|
||||
private final URL url;
|
||||
|
||||
private final URLClassLoader classLoader;
|
||||
|
||||
private final Map<String, Class<?>> classCache = new ConcurrentHashMap<>();
|
||||
private final Map<String, CacheWrapper> classCache = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
private final SandboxSecurityManager securityManager = new SandboxSecurityManager(classCache);
|
||||
@@ -50,53 +47,31 @@ public class JavaCodeEngine {
|
||||
private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||
|
||||
|
||||
public JavaCodeEngine(JavaCodeProperties javaCodeProperties) {
|
||||
public JavaCodeEngine(String classPath) {
|
||||
try {
|
||||
CLASS_PATH = javaCodeProperties.getClassPath();
|
||||
CLASS_PATH = classPath;
|
||||
File file = new File(CLASS_PATH);
|
||||
if (!file.exists()) {
|
||||
file.mkdirs();
|
||||
}
|
||||
url = file.toURI().toURL();
|
||||
classLoader = new SandboxClassLoader(new URL[]{url});
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 执行方法
|
||||
*
|
||||
* @param code 代码字符串
|
||||
* @param className 类名
|
||||
* @param methodName 方法名
|
||||
* @param paramClass 参数类型数组
|
||||
* @param args 参数数组
|
||||
* @param development 是否为开发环境 开发环境下会将生成的类在执行完成后删除,不是生产环境则会缓存提高运行效率
|
||||
* @return 执行结果
|
||||
*/
|
||||
public EngineResult execute(String code, String className, String methodName, Class<?>[] paramClass, Object[] args, boolean development) {
|
||||
if (development) {
|
||||
return developmentExecute(code, className, methodName, paramClass, args);
|
||||
} else {
|
||||
return onlineExecute(code, className, methodName, paramClass, args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用于在开发环境中执行代码的私有方法。
|
||||
*
|
||||
* @param code 需要执行的代码字符串
|
||||
* @param className 类名
|
||||
* @param methodName 方法名
|
||||
* @param paramClass 参数类型数组
|
||||
* @param args 参数数组
|
||||
* @return 执行结构
|
||||
*/
|
||||
@SneakyThrows
|
||||
private EngineResult developmentExecute(String code, String className, String methodName, Class<?>[] paramClass, Object[] args) {
|
||||
public EngineResult mockExecute(String code, String className, String methodName, Object[] args) {
|
||||
Class<?> loadClass = null;
|
||||
try {
|
||||
// 加锁,确保类只加载一次
|
||||
@@ -106,12 +81,12 @@ public class JavaCodeEngine {
|
||||
// 创建一个URLClassLoader,用于加载代码字符串
|
||||
tempClassLoader = new URLClassLoader(new URL[]{url});
|
||||
// 编译代码字符串为类
|
||||
Class<?> tempClass = compilerClass(className, code, tempClassLoader);
|
||||
// 将编译好的类放入缓存
|
||||
classCache.put(className, tempClass);
|
||||
return tempClass;
|
||||
return compilerClass(className, code, tempClassLoader);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
if (e instanceof CustomException) {
|
||||
throw (CustomException) e;
|
||||
}
|
||||
// 异常处理,并抛出自定义的SandboxClassNotFoundException异常
|
||||
throw new SandboxClassNotFoundException(e.getMessage());
|
||||
} finally {
|
||||
@@ -121,15 +96,17 @@ public class JavaCodeEngine {
|
||||
}
|
||||
});
|
||||
// 获取需要执行的方法
|
||||
Method method = loadClass.getMethod(methodName, paramClass);
|
||||
Method method = getMethod(methodName, loadClass);
|
||||
// 设置安全检查器
|
||||
System.setSecurityManager(securityManager);
|
||||
// 执行方法并返回结果
|
||||
return MultiThreadedCapture.capture(() -> method.invoke(null, args));
|
||||
|
||||
return ConsoleCapture.capture(() -> method.invoke(null, args));
|
||||
} catch (CustomException e) {
|
||||
EngineResult result = new EngineResult();
|
||||
result.setSuccess(Boolean.FALSE);
|
||||
result.setConsole(e.getMessage());
|
||||
return result;
|
||||
} finally {
|
||||
// 从缓存中移除编译好的类
|
||||
classCache.remove(className);
|
||||
// 清空安全检查器
|
||||
System.setSecurityManager(null);
|
||||
if (loadClass != null) {
|
||||
@@ -156,37 +133,53 @@ public class JavaCodeEngine {
|
||||
* @param code 需要执行的代码字符串
|
||||
* @param className 类名
|
||||
* @param methodName 方法名
|
||||
* @param paramClass 参数类型数组
|
||||
* @param args 参数数组
|
||||
* @return 执行结构
|
||||
*/
|
||||
private EngineResult onlineExecute(String code, String className, String methodName, Class<?>[] paramClass, Object[] args) {
|
||||
public Object execute(String code, String className, String methodName, Object[] args) {
|
||||
try {
|
||||
Class<?> loadClass = null;
|
||||
loadClass = classCache.get(className);
|
||||
if (loadClass == null) {
|
||||
loadClass = getLoadClass(code, className);
|
||||
Method method = loadClass.getMethod(methodName, paramClass);
|
||||
System.setSecurityManager(securityManager);
|
||||
Object result = (Object) method.invoke(null, args);
|
||||
return new EngineResult(result);
|
||||
//从缓存中获取
|
||||
CacheWrapper wrapper = classCache.get(className);
|
||||
//缓存中不存在
|
||||
if (wrapper == null) {
|
||||
//加载
|
||||
wrapper = getLoadClass(code, className);
|
||||
}
|
||||
//获取到类信息
|
||||
loadClass = wrapper.getClazz();
|
||||
//获取方法
|
||||
Method method = getMethod(methodName, loadClass);
|
||||
//开启安全模式
|
||||
System.setSecurityManager(securityManager);
|
||||
//执行方法
|
||||
return method.invoke(null, args);
|
||||
} catch (Exception e) {
|
||||
remove(className);
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
System.setSecurityManager(null);
|
||||
File javaFile = new File(CLASS_PATH + className + JAVA_SUFFIX);
|
||||
if (javaFile.exists()) {
|
||||
javaFile.delete();
|
||||
}
|
||||
File classFile = new File(CLASS_PATH + className + CLASS_SUFFIX);
|
||||
if (classFile.exists()) {
|
||||
classFile.delete();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到方法
|
||||
*
|
||||
* @param methodName 方法名称
|
||||
* @param loadClass 类信息
|
||||
* @return 方法对象
|
||||
*/
|
||||
private Method getMethod(String methodName, Class<?> loadClass) {
|
||||
Method method = null;
|
||||
for (Method declaredMethod : loadClass.getDeclaredMethods()) {
|
||||
if (declaredMethod.getName().equals(methodName)) {
|
||||
method = declaredMethod;
|
||||
}
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到编译完成的Class对象
|
||||
*
|
||||
@@ -194,15 +187,18 @@ public class JavaCodeEngine {
|
||||
* @param className 类名
|
||||
* @return 编译后的Java对象
|
||||
*/
|
||||
private Class<?> getLoadClass(String code, String className) {
|
||||
private CacheWrapper getLoadClass(String code, String className) {
|
||||
//使用分段锁,提高效率,放多并发情况下多次对同一个类进行编译
|
||||
return SegmentLock.lock(className, () -> {
|
||||
try {
|
||||
URLClassLoader classLoader = new SandboxClassLoader(new URL[]{url});
|
||||
//执行编译
|
||||
Class<?> tempClass = compilerClass(className, code, classLoader);
|
||||
//创建缓存包装对象
|
||||
CacheWrapper wrapper = new CacheWrapper(tempClass, classLoader);
|
||||
//将编译之后的类对象放在缓存中,提高线上环境的运行效率
|
||||
classCache.put(className, tempClass);
|
||||
return tempClass;
|
||||
classCache.put(className, wrapper);
|
||||
return wrapper;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -248,11 +244,19 @@ public class JavaCodeEngine {
|
||||
*/
|
||||
public Boolean remove(String className) {
|
||||
return SegmentLock.lock(className, () -> {
|
||||
classCache.remove(className);
|
||||
CacheWrapper wrapper = classCache.get(className);
|
||||
if (wrapper != null) {
|
||||
classCache.remove(className);
|
||||
wrapper.remove();
|
||||
}
|
||||
//进行gc 垃圾挥手
|
||||
System.gc();
|
||||
//删除Java文件
|
||||
File javaFile = new File(CLASS_PATH + className + JAVA_SUFFIX);
|
||||
if (javaFile.exists()) {
|
||||
javaFile.delete();
|
||||
}
|
||||
//删除class文件
|
||||
File classFile = new File(CLASS_PATH + className + CLASS_SUFFIX);
|
||||
if (classFile.exists()) {
|
||||
classFile.delete();
|
||||
@@ -260,4 +264,24 @@ public class JavaCodeEngine {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Getter
|
||||
public static class CacheWrapper {
|
||||
|
||||
private Class<?> clazz;
|
||||
|
||||
private URLClassLoader classLoader;
|
||||
|
||||
public CacheWrapper(Class<?> clazz, URLClassLoader classLoader) {
|
||||
this.clazz = clazz;
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
|
||||
public void remove(){
|
||||
clazz = null;
|
||||
classLoader = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package cn.fateverse.common.code.engine;
|
||||
|
||||
import cn.fateverse.common.code.console.MultiThreadedCapture;
|
||||
import cn.fateverse.common.code.console.ConsoleCapture;
|
||||
import cn.fateverse.common.code.lock.SegmentLock;
|
||||
import cn.fateverse.common.code.model.EngineResult;
|
||||
import cn.fateverse.common.core.exception.CustomException;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Value;
|
||||
import org.springframework.security.core.parameters.P;
|
||||
|
||||
import javax.script.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -42,9 +40,16 @@ public class JavaScriptEngine {
|
||||
* @param args 参数
|
||||
* @return 返回结构
|
||||
*/
|
||||
public static EngineResult execute(String script, String functionName, boolean development, Object args) {
|
||||
if (development) {
|
||||
return MultiThreadedCapture.capture(() -> {
|
||||
public static Object execute(String script, String functionName, Object args) {
|
||||
Value executeFunction = getFunction(functionName, script);
|
||||
Value result = executeFunction.execute(JSON.toJSONString(args));
|
||||
return result.as(Object.class);
|
||||
}
|
||||
|
||||
|
||||
public static EngineResult mockExecute(String script, String functionName, Object args) {
|
||||
try {
|
||||
return ConsoleCapture.capture(() -> {
|
||||
Context context = Context.newBuilder()
|
||||
.allowAllAccess(true)
|
||||
// .allowHostClassLoading(true)
|
||||
@@ -63,10 +68,11 @@ public class JavaScriptEngine {
|
||||
Value result = executeFunction.execute(javaObjectAsValue);
|
||||
return result.as(Object.class);
|
||||
});
|
||||
} else {
|
||||
Value executeFunction = getFunction(functionName, script);
|
||||
Value result = executeFunction.execute(JSON.toJSONString(args));
|
||||
return new EngineResult(result.as(Object.class));
|
||||
}catch (CustomException e){
|
||||
EngineResult result = new EngineResult();
|
||||
result.setSuccess(Boolean.FALSE);
|
||||
result.setConsole(e.getMessage());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 分段锁对象
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2023-10-25
|
||||
*/
|
||||
@@ -15,10 +17,11 @@ public class SegmentLock {
|
||||
|
||||
/**
|
||||
* 分段锁
|
||||
* @param key 锁名称
|
||||
*
|
||||
* @param key 锁名称
|
||||
* @param supplier 需要执行的函数
|
||||
* @param <T> 接收泛型
|
||||
* @return 执行后的结果
|
||||
* @param <T> 接收泛型
|
||||
*/
|
||||
public static <T> T lock(String key, Supplier<T> supplier) {
|
||||
ReentrantLock lock = lockMap.get(key);
|
||||
|
||||
@@ -18,8 +18,11 @@ public class EngineResult {
|
||||
|
||||
private String console;
|
||||
|
||||
private Boolean success;
|
||||
|
||||
public EngineResult(Object result) {
|
||||
public EngineResult(Object result, String console) {
|
||||
success = true;
|
||||
this.result = result;
|
||||
this.console = console;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package cn.fateverse.common.code.sandbox;
|
||||
|
||||
import cn.fateverse.common.code.engine.JavaCodeEngine;
|
||||
|
||||
import java.io.FilePermission;
|
||||
import java.lang.reflect.ReflectPermission;
|
||||
import java.security.Permission;
|
||||
@@ -9,9 +11,9 @@ import java.util.Set;
|
||||
|
||||
public class SandboxSecurityManager extends SecurityManager {
|
||||
|
||||
private final Map<String, Class<?>> classCache;
|
||||
private final Map<String, JavaCodeEngine.CacheWrapper> classCache;
|
||||
|
||||
public SandboxSecurityManager(Map<String, Class<?>> classCache) {
|
||||
public SandboxSecurityManager(Map<String, JavaCodeEngine.CacheWrapper> classCache) {
|
||||
this.classCache = classCache;
|
||||
}
|
||||
|
||||
@@ -38,7 +40,7 @@ public class SandboxSecurityManager extends SecurityManager {
|
||||
|
||||
private boolean isAllowedPermission(Permission permission) {
|
||||
//权限:用于校验文件系统访问权限,包括读取、写入、删除文件,以及目录操作。权限名称可能包括文件路径和操作,如 "read", "write", "delete", "execute" 等。
|
||||
if (permission instanceof FilePermission){
|
||||
if (permission instanceof FilePermission) {
|
||||
System.out.println("触发文件读写权限");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package cn.fateverse.common.security.filter;
|
||||
|
||||
import cn.fateverse.common.core.utils.ObjectUtils;
|
||||
import cn.fateverse.common.security.service.TokenService;
|
||||
import cn.fateverse.common.security.entity.LoginUser;
|
||||
import cn.fateverse.common.security.utils.SecurityUtils;
|
||||
@@ -8,6 +7,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
Reference in New Issue
Block a user