新增AsgcCompiler封装.

This commit is contained in:
aoshiguchen
2022-08-17 22:20:20 +08:00
parent 3d80dd7836
commit b9b29d6406
8 changed files with 415 additions and 7 deletions
@@ -0,0 +1,124 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.aop.compiler;
import com.google.common.collect.Lists;
import fun.asgc.neutrino.core.base.GlobalConfig;
import fun.asgc.neutrino.core.util.CollectionUtil;
import lombok.extern.slf4j.Slf4j;
import javax.tools.*;
import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
*
* @author: aoshiguchen
* @date: 2022/8/17
*/
@Slf4j
@SuppressWarnings("all")
public class AsgcCompiler {
private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
private DiagnosticCollector<JavaFileObject> collector;
private List<String> classpathList;
private boolean isSaveSourceCodeFile;
private boolean isSaveClassFile;
private String generatorCodeSavePath;
private ByteCodeClassLoader classLoader;
public AsgcCompiler() {
if (null == compiler) {
throw new RuntimeException("Can not get javax.tools.JavaCompiler, check whether \"tools.jar\" is in the environment variable CLASSPATH \nVisit https://jfinal.com/doc/4-8 for details \n");
}
this.collector = new DiagnosticCollector<>();
this.classpathList = Lists.newArrayList();
this.isSaveClassFile = false;
this.generatorCodeSavePath = GlobalConfig.getGeneratorCodeSavePath();
this.classLoader = new ByteCodeClassLoader();
}
public void addClasspath(String classpath) {
this.classpathList.add(classpath);
}
public void setSaveClassFile(boolean saveClassFile) {
this.isSaveClassFile = saveClassFile;
}
public void setSaveSourceCodeFile(boolean saveSourceCodeFile) {
isSaveSourceCodeFile = saveSourceCodeFile;
}
public void setGeneratorCodeSavePath(String generatorCodeSavePath) {
this.generatorCodeSavePath = generatorCodeSavePath;
}
private List<String> getOptions() {
List<String> options = Lists.newArrayList();
options.add("-target");
options.add("1.8");
if (!CollectionUtil.isEmpty(classpathList)) {
options.add("-classpath");
options.add(classpathList.stream().collect(Collectors.joining(File.pathSeparator)));
}
return options;
}
/**
* 编译代码
* @param className 类名
* @param sourceCode 源代码
*/
public Map<String,byte[]> compile(String className, String sourceCode) {
DynamicJavaFileManager javaFileManager = new DynamicJavaFileManager(compiler.getStandardFileManager(collector, null, null));
CharSequenceJavaFileObject javaFileObject = new CharSequenceJavaFileObject(className, sourceCode);
Boolean result = compiler.getTask(null, javaFileManager, collector, getOptions(), null, Arrays.asList(javaFileObject)).call();
if (!result) {
collector.getDiagnostics().forEach(item -> log.error(item.toString()));
}
Map<String, byte[]> ret = new HashMap<>();
for (Map.Entry<String, CharSequenceJavaFileObject> e : javaFileManager.fileObjects.entrySet()) {
ret.put(e.getKey(), e.getValue().getByteCode());
}
return ret;
}
/**
* 编译并加载类
* @param pkg
* @param className
* @param sourceCode
* @param <T>
* @return
*/
public <T> Class<T> compileAndLoadClass(String pkg, String className, String sourceCode) throws ClassNotFoundException {
Map<String,byte[]> byteCodeMap = compile(className, sourceCode);
classLoader.addByteCode(byteCodeMap);
return (Class<T>)classLoader.loadClass(pkg + "." + className);
}
}
@@ -0,0 +1,67 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.aop.compiler;
import fun.asgc.neutrino.core.util.CollectionUtil;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
*
* @author: aoshiguchen
* @date: 2022/8/17
*/
public class ByteCodeClassLoader extends ClassLoader {
private Map<String, byte[]> byteCodeMap = new ConcurrentHashMap<>();
static {
registerAsParallelCapable();
}
public ByteCodeClassLoader() {
super(getParentClassLoader());
}
protected static ClassLoader getParentClassLoader() {
ClassLoader ret = Thread.currentThread().getContextClassLoader();
return ret != null ? ret : ByteCodeClassLoader.class.getClassLoader();
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = byteCodeMap.get(name);
if (null != bytes) {
Class<?> ret = defineClass(name, bytes, 0, bytes.length);
byteCodeMap.remove(name);
return ret;
}
return super.findClass(name);
}
public void addByteCode(Map<String, byte[]> byteCodeMap) {
if (!CollectionUtil.isEmpty(byteCodeMap)) {
for (Map.Entry<String, byte[]> e : byteCodeMap.entrySet()) {
this.byteCodeMap.putIfAbsent(e.getKey(), e.getValue());
}
}
}
}
@@ -0,0 +1,77 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.aop.compiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
/**
*
* @author: aoshiguchen
* @date: 2022/8/17
*/
class CharSequenceJavaFileObject extends SimpleJavaFileObject {
private final CharSequence sourceCode;
private ByteArrayOutputStream out;
public CharSequenceJavaFileObject(URI uri, Kind kind) {
super(uri, kind);
this.sourceCode = null;
}
public CharSequenceJavaFileObject(String name, String source) {
super(URI.create(name + JavaFileObject.Kind.SOURCE.extension), JavaFileObject.Kind.SOURCE);
this.sourceCode = source;
}
public CharSequenceJavaFileObject(String name, JavaFileObject.Kind kind) {
super(URI.create(name + kind.extension), kind);
this.sourceCode = null;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
if (this.sourceCode == null) {
throw new IllegalStateException("源代码不能为空!");
}
return sourceCode;
}
@Override
public OutputStream openOutputStream() throws IOException {
if (null == out) {
this.out = new ByteArrayOutputStream();
}
return out;
}
public byte[] getByteCode() {
if (null == out) {
return null;
}
return out.toByteArray();
}
}
@@ -0,0 +1,59 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.aop.compiler;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author: aoshiguchen
* @date: 2022/8/17
*/
public class DynamicJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
public Map<String, CharSequenceJavaFileObject> fileObjects = new HashMap<>();
public DynamicJavaFileManager(JavaFileManager fileManager) {
super(fileManager);
}
@Override
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String qualifiedClassName, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
CharSequenceJavaFileObject javaFileObject = new CharSequenceJavaFileObject(qualifiedClassName, kind);
this.fileObjects.put(qualifiedClassName, javaFileObject);
return javaFileObject;
}
@Override
public JavaFileObject getJavaFileForInput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind) throws IOException {
JavaFileObject javaFileObject = fileObjects.get(className);
if (javaFileObject == null) {
javaFileObject = super.getJavaFileForInput(location, className, kind);
}
return javaFileObject;
}
}
@@ -61,4 +61,8 @@ public class ProxyClass {
this.target = target;
this.pkg = target.getPackage().getName();
}
public ProxyClass() {
}
}
@@ -28,7 +28,6 @@ import lombok.extern.slf4j.Slf4j;
import javax.tools.*;
import java.io.*;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
@@ -89,7 +88,7 @@ public class ProxyCompiler {
}
int index = 0;
boolean isWindows = isWindows();
boolean isWindows = SystemUtil.isWindows();
StringBuilder ret = new StringBuilder();
for (URL url : classLoader.getURLs()) {
if (index++ > 0) {
@@ -124,11 +123,6 @@ public class ProxyCompiler {
return list.stream().collect(Collectors.joining(File.pathSeparator));
}
protected boolean isWindows() {
String osName = System.getProperty("os.name", "unknown");
return osName.toLowerCase().indexOf("windows") != -1;
}
protected URLClassLoader getURLClassLoader() {
ClassLoader ret = Thread.currentThread().getContextClassLoader();
if (ret == null) {
@@ -107,4 +107,9 @@ public class SystemUtil {
}
return url;
}
public static boolean isWindows() {
String osName = System.getProperty("os.name", "unknown");
return osName.toLowerCase().indexOf("windows") != -1;
}
}
@@ -0,0 +1,78 @@
/**
* Copyright (c) 2022 aoshiguchen
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package fun.asgc.neutrino.core.aop.compiler;
import fun.asgc.neutrino.core.aop.proxy.ProxyClass;
import fun.asgc.neutrino.core.aop.proxy.ProxyClassLoader;
import fun.asgc.neutrino.core.aop.proxy.ProxyCompiler;
import fun.asgc.neutrino.core.util.ReflectUtil;
import org.junit.Test;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
*
* @author: aoshiguchen
* @date: 2022/8/17
*/
public class AsgcCompilerTest {
@Test
public void test1() throws IllegalAccessException, InstantiationException, InvocationTargetException {
AsgcCompiler compiler = new AsgcCompiler();
String code = "package a.b;\n" +
"public class Hello {\n" +
"\tpublic void hello() {\n" +
"\t\tSystem.out.println(\"hello\");\n" +
"\t}\n" +
"}\n";
ProxyClass proxyClass = new ProxyClass();
proxyClass.setPkg("a.b");
proxyClass.setName("Hello");
proxyClass.setSourceCode(code);
ProxyCompiler proxyCompiler = new ProxyCompiler();
proxyCompiler.compile(proxyClass);
ProxyClassLoader proxyClassLoader = new ProxyClassLoader();
Class clazz = proxyClassLoader.loadProxyClass(proxyClass);
Method method = ReflectUtil.getMethods(clazz).stream().filter(m -> m.getName().equals("hello")).findFirst().get();
Object instance = clazz.newInstance();
method.invoke(instance);
}
@Test
public void test2() throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException {
AsgcCompiler compiler = new AsgcCompiler();
String code = "package a.b;\n" +
"public class Hello {\n" +
"\tpublic void hello() {\n" +
"\t\tSystem.out.println(\"hello\");\n" +
"\t}\n" +
"}\n";
Class clazz = compiler.compileAndLoadClass("a.b","Hello", code);
Method method = ReflectUtil.getMethods(clazz).stream().filter(m -> m.getName().equals("hello")).findFirst().get();
Object instance = clazz.newInstance();
method.invoke(instance);
}
}