diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/interceptor/InterceptorFactory.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/interceptor/InterceptorFactory.java index a521d914..8f4f223e 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/interceptor/InterceptorFactory.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/interceptor/InterceptorFactory.java @@ -67,7 +67,7 @@ public class InterceptorFactory { () -> { try { Object obj = null; - if (GlobalConfig.isIsContainerStartup()) { + if (GlobalConfig.isContainerStartup()) { obj = BeanManager.getBean(clazz); } if (null == obj) { diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/AsgcProxyFactory.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/AsgcProxyFactory.java index 36e4d6d3..030fb384 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/AsgcProxyFactory.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/AsgcProxyFactory.java @@ -82,16 +82,23 @@ public class AsgcProxyFactory implements ProxyFactory { proxyClass.setName(generateClassName(targetType)); String sourceCode = AsgcProxyGenerator.getInstance().generator(proxyClass.getName(), targetType, proxyType); proxyClass.setSourceCode(sourceCode); - if (GlobalConfig.isIsPrintGeneratorCode()) { + if (GlobalConfig.isPrintGeneratorCode()) { log.debug("类:{} 的代理类源码:\n{}", targetType.getName(), sourceCode); } - - compiler.compile(proxyClass); - Class

retClass = (Class

)classLoader.loadProxyClass(proxyClass); + Class

retClass = compile(proxyClass); P obj = retClass.newInstance(); return obj; } + private Class compile(ProxyClass proxyClass) throws ClassNotFoundException { + if (SystemUtil.isStartupFromJar() || GlobalConfig.isSaveGeneratorCode()) { + compiler.compileToFile(proxyClass); + return (Class)classLoader.loadProxyClass(proxyClass); + } + compiler.compile(proxyClass); + return (Class)classLoader.loadProxyClass(proxyClass); + } + private String generateClassName(Class clazz) { return String.format(classNameTemplate, clazz.getSimpleName(), proxyClassCounter.incrementAndGet()); } diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyClassLoader.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyClassLoader.java index e6e51e6b..016ee66b 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyClassLoader.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyClassLoader.java @@ -21,6 +21,9 @@ */ package fun.asgc.neutrino.core.aop.proxy; +import fun.asgc.neutrino.core.base.GlobalConfig; +import fun.asgc.neutrino.core.util.FileUtil; + import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -47,8 +50,10 @@ public class ProxyClassLoader extends ClassLoader { } public Class loadProxyClass(ProxyClass proxyClass) { - for (Map.Entry e : proxyClass.getByteCode().entrySet()) { - byteCodeMap.putIfAbsent(e.getKey(), e.getValue()); + if (null != proxyClass.getByteCode()) { + for (Map.Entry e : proxyClass.getByteCode().entrySet()) { + byteCodeMap.putIfAbsent(e.getKey(), e.getValue()); + } } try { @@ -61,6 +66,9 @@ public class ProxyClassLoader extends ClassLoader { @Override protected Class findClass(String name) throws ClassNotFoundException { byte[] bytes = byteCodeMap.get(name); + if (null == bytes) { + bytes = FileUtil.readBytes( GlobalConfig.getGeneratorCodeSavePath() + name.replaceAll("\\.", "/") + ".class"); + } if (bytes != null) { Class ret = defineClass(name, bytes, 0, bytes.length); byteCodeMap.remove(name); diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyCompiler.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyCompiler.java index d7f0fb24..47293233 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyCompiler.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/aop/proxy/ProxyCompiler.java @@ -21,25 +21,39 @@ */ package fun.asgc.neutrino.core.aop.proxy; +import fun.asgc.neutrino.core.base.GlobalConfig; +import fun.asgc.neutrino.core.util.FileUtil; +import fun.asgc.neutrino.core.util.SystemUtil; +import lombok.extern.slf4j.Slf4j; + import javax.tools.*; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; +import java.io.*; +import java.lang.reflect.Method; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.util.*; +import java.util.stream.Collectors; /** * * @author: aoshiguchen * @date: 2022/6/24 */ +@Slf4j public class ProxyCompiler { + /** + * 收集编译过程信息 + */ + private static DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + protected volatile List options = null; + protected volatile boolean isUnpack = false; + protected List getOptions() { + + if (options != null) { return options; } @@ -57,6 +71,8 @@ public class ProxyCompiler { if (cp != null && cp.trim().length() != 0) { ret.add("-classpath"); ret.add(cp); + + System.out.println("classpath:" + cp); } options = ret; @@ -68,35 +84,46 @@ public class ProxyCompiler { * 兼容 tomcat 丢失 class path,否则无法编译 */ protected String getClassPath() { - URLClassLoader classLoader = getURLClassLoader(); - if (classLoader == null) { - return null; + if (!SystemUtil.isStartupFromJar()) { + URLClassLoader classLoader = getURLClassLoader(); + if (classLoader == null) { + return null; + } + + int index = 0; + boolean isWindows = isWindows(); + StringBuilder ret = new StringBuilder(); + for (URL url : classLoader.getURLs()) { + if (index++ > 0) { + ret.append(File.pathSeparator); + } + + String path = url.getFile(); + + // 如果是 windows 系统,去除前缀字符 '/' + if (isWindows && path.startsWith("/")) { + path = path.substring(1); + } + + // 去除后缀字符 '/' + if (path.length() > 1 && (path.endsWith("/") || path.endsWith(File.separator))) { + path = path.substring(0, path.length() - 1); + } + ret.append(path); + } + + return ret.toString(); } - - int index = 0; - boolean isWindows = isWindows(); - StringBuilder ret = new StringBuilder(); - for (URL url : classLoader.getURLs()) { - if (index++ > 0) { - ret.append(File.pathSeparator); + List list = new ArrayList<>(); + File file = new File(GlobalConfig.getGeneratorCodeSavePath() + "BOOT-INF/lib/"); + if (file.exists()) { + File[] files = file.listFiles(); + for (File f : files) { + list.add(f.getAbsolutePath()); } - - String path = url.getFile(); - - // 如果是 windows 系统,去除前缀字符 '/' - if (isWindows && path.startsWith("/")) { - path = path.substring(1); - } - - // 去除后缀字符 '/' - if (path.length() > 1 && (path.endsWith("/") || path.endsWith(File.separator))) { - path = path.substring(0, path.length() - 1); - } - - ret.append(path); } - - return ret.toString(); + list.add(GlobalConfig.getGeneratorCodeSavePath() + "BOOT-INF/classes/"); + return list.stream().collect(Collectors.joining(File.pathSeparator)); } protected boolean isWindows() { @@ -137,6 +164,67 @@ public class ProxyCompiler { } } + public void compileToFile(ProxyClass proxyClass) { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + if (compiler == null) { + throw new RuntimeException("Can not get javax.tools.JavaCompiler, check whether \"tools.jar\" is in the environment variable CLASSPATH \n" + + "Visit https://jfinal.com/doc/4-8 for details \n"); + } + unpack(); + + File file = FileUtil.save(GlobalConfig.getGeneratorCodeSavePath() + proxyClass.getPkg().replaceAll("\\.", "/"), proxyClass.getName() + ".java", proxyClass.getSourceCode()); + StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(diagnostics, null, null); + + Iterable iterable = standardFileManager.getJavaFileObjects(file); + // 创建一个编译任务 + JavaCompiler.CompilationTask task = compiler.getTask(null, standardFileManager, diagnostics, getOptions(), null, iterable); + //JavaCompiler.CompilationTask 实现了 Callable 接口 + Boolean result = task.call(); + printLog(result, file); + } + + private synchronized void unpack() { + if (!SystemUtil.isStartupFromJar()) { + return; + } + if (isUnpack) { + return; + } + isUnpack = true; + try { + File file1 = new File(GlobalConfig.getGeneratorCodeSavePath() + "BOOT-INF/lib/"); + File file2 = new File(GlobalConfig.getGeneratorCodeSavePath() + "BOOT-INF/classes/"); + if (file1.exists() && file2.exists()) { + return; + } + log.info("解压jar:{} 到:{}", SystemUtil.getCurrentJarFilePath(), GlobalConfig.getGeneratorCodeSavePath()); + FileUtil.unzipJar(GlobalConfig.getGeneratorCodeSavePath(), SystemUtil.getCurrentJarFilePath()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void printLog(Boolean result, File ...files){ + if (!result) { + StringJoiner rs = new StringJoiner(System.getProperty("line.separator")); + for (Diagnostic diagnostic : diagnostics.getDiagnostics()) { + rs.add(String.format("%s:%s[line %d column %d]-->%s%n", diagnostic.getKind(), diagnostic.getSource(), diagnostic.getLineNumber(), + diagnostic.getColumnNumber(), + diagnostic.getMessage(null))); + } + System.out.println("编译失败,原因:" + rs.toString()); + log.error("编译失败,原因:{}", rs.toString()); + } else { + System.out.println("编译成功"); + StringBuilder sb = new StringBuilder(); + Arrays.stream(files).forEach(file -> { + sb.append(file.getName()); + sb.append(";"); + }); + log.info("编译成功:{}", sb.toString()); + } + } + protected void outputCompileError(Boolean result, DiagnosticCollector collector) { if (! result) { // collector.getDiagnostics().forEach(item -> log.error(item.toString())); diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/GlobalConfig.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/GlobalConfig.java index 134d5f46..63e84c8e 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/GlobalConfig.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/GlobalConfig.java @@ -35,8 +35,19 @@ public class GlobalConfig { * 是否是容器启动 */ private static volatile boolean isContainerStartup = false; + /** + * 是否保存生成的代码 + * 只是一个建议。 + * 当程序是普通运行时,采纳建议。 + * 当程序是以jar方式运行时,强制保存生成的代码 + */ + private static volatile boolean isSaveGeneratorCode = false; + /** + * 自动生成代码保存路径 + */ + private static volatile String generatorCodeSavePath = "./lib/"; - public static boolean isIsPrintGeneratorCode() { + public static boolean isPrintGeneratorCode() { return isPrintGeneratorCode; } @@ -44,11 +55,27 @@ public class GlobalConfig { GlobalConfig.isPrintGeneratorCode = isPrintGeneratorCode; } - public static boolean isIsContainerStartup() { + public static boolean isContainerStartup() { return isContainerStartup; } public static void setIsContainerStartup(boolean isContainerStartup) { GlobalConfig.isContainerStartup = isContainerStartup; } + + public static boolean isSaveGeneratorCode() { + return isSaveGeneratorCode; + } + + public static void setIsSaveGeneratorCode(boolean isSaveGeneratorCode) { + GlobalConfig.isSaveGeneratorCode = isSaveGeneratorCode; + } + + public static String getGeneratorCodeSavePath() { + return generatorCodeSavePath; + } + + public static void setGeneratorCodeSavePath(String generatorCodeSavePath) { + GlobalConfig.generatorCodeSavePath = generatorCodeSavePath; + } } diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/FileUtil.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/FileUtil.java index e3e1fac9..772e3cfe 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/FileUtil.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/FileUtil.java @@ -28,6 +28,13 @@ import org.apache.commons.io.FileDeleteStrategy; import org.apache.commons.lang3.StringUtils; import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; import java.util.stream.Collectors; /** @@ -50,7 +57,6 @@ public class FileUtil { String subPath = path.substring(MetaDataConstant.CLASSPATH_RESOURCE_IDENTIFIER.length()); return FileUtil.class.getResourceAsStream(subPath); } - return new FileInputStream(path); } @@ -103,6 +109,11 @@ public class FileUtil { } public static void write(String path, String content) { +// try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(path), StandardCharsets.UTF_8)){ +// writer.write(content); +// } catch (Exception e) { +// e.printStackTrace(); +// } try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(getOutputStream(path)))){ bw.write(content); } catch (IOException e) { @@ -147,4 +158,39 @@ public class FileUtil { return true; } + public static File save(String path, String fileName, String content) { + if (!path.endsWith("/")) { + path += "/"; + } + makeDirs(path); + write(path + fileName, content); + return new File(path + "/" + fileName); + } + + public static void unzipJar(String destinationDir, String jarPath) throws IOException { + File file = new File(jarPath); + JarFile jar = new JarFile(file); + for (Enumeration enums = jar.entries(); enums.hasMoreElements();) { + JarEntry entry = (JarEntry) enums.nextElement(); + String fileName = destinationDir + File.separator + entry.getName(); + File f = new File(fileName); + if (fileName.endsWith("/")) { + f.mkdirs(); + } + } + for (Enumeration enums = jar.entries(); enums.hasMoreElements();) { + JarEntry entry = (JarEntry) enums.nextElement(); + String fileName = destinationDir + File.separator + entry.getName(); + File f = new File(fileName); + if (!fileName.endsWith("/")) { + InputStream is = jar.getInputStream(entry); + FileOutputStream fos = new FileOutputStream(f); + while (is.available() > 0) { + fos.write(is.read()); + } + fos.close(); + is.close(); + } + } + } } diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/SystemUtil.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/SystemUtil.java index 9d166f42..ed9dc018 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/SystemUtil.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/SystemUtil.java @@ -24,6 +24,8 @@ package fun.asgc.neutrino.core.util; import fun.asgc.neutrino.core.base.CodeBlock; +import java.util.Objects; + /** * * @author: aoshiguchen @@ -85,6 +87,24 @@ public class SystemUtil { private void stop() { this.running = false; } + } + public static boolean isStartupFromJar() { + String protocol = SystemUtil.class.getResource("").getProtocol(); + return Objects.equals(protocol, "jar"); + } + + public static String getCurrentJarFilePath() { + if (!isStartupFromJar()) { + return null; + } + String url = SystemUtil.class.getResource("").getPath(); + if (url.startsWith("file:")) { + url = url.substring(5); + } + if (url.contains("!")) { + url = url.substring(0, url.indexOf("!")); + } + return url; } } diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/ProxyServer.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/ProxyServer.java index 83541167..d8838649 100644 --- a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/ProxyServer.java +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/ProxyServer.java @@ -23,7 +23,9 @@ package fun.asgc.neutrino.proxy.server; import fun.asgc.neutrino.core.annotation.NeutrinoApplication; +import fun.asgc.neutrino.core.base.GlobalConfig; import fun.asgc.neutrino.core.context.NeutrinoLauncher; +import fun.asgc.neutrino.core.util.SystemUtil; /** * @@ -34,6 +36,7 @@ import fun.asgc.neutrino.core.context.NeutrinoLauncher; public class ProxyServer { public static void main(String[] args) { + GlobalConfig.setIsSaveGeneratorCode(true); NeutrinoLauncher.run(ProxyServer.class, args).sync(); }