feat: 优化编译逻辑

This commit is contained in:
wangyu 2023-09-13 14:10:35 +08:00
parent 38811b4f93
commit 96aa81d9b9
22 changed files with 262 additions and 161 deletions

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>flyfish-framework</artifactId>
<groupId>com.flyfish.framework</groupId>
<version>0.0.1-SNAPSHOT</version>
<version>0.0.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>flyfish-framework</artifactId>
<groupId>com.flyfish.framework</groupId>
<version>0.0.1-SNAPSHOT</version>
<version>0.0.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>flyfish-framework</artifactId>
<groupId>com.flyfish.framework</groupId>
<version>0.0.1-SNAPSHOT</version>
<version>0.0.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -2,6 +2,7 @@ package com.flyfish.framework.compiler;
import com.flyfish.framework.compiler.support.DelegateJavaCompiler;
import com.flyfish.framework.compiler.support.SimpleJavaCompiler;
import org.springframework.lang.Nullable;
import java.io.Closeable;
import java.io.IOException;
@ -37,7 +38,18 @@ public interface DynamicJavaCompiler extends Closeable {
*
* @param name 文件名
* @param source 源码
* @return 完整类名
*/
@Nullable
String compile(String name, String source) throws IOException;
/**
* 加载类
* 用于全部编译后再加载
*
* @param name 名称
* @return 结果
*/
Class<?> compile(String name, String source) throws ClassNotFoundException, IOException;
@Nullable
Class<?> load(String name);
}

View File

@ -7,9 +7,9 @@ package com.flyfish.framework.compiler.core;
*/
public interface ClassLoaders {
MemoryClassLoader CLASS_LOADER = new MemoryClassLoader();
DynamicClassLoader CLASS_LOADER = new MemoryClassLoader();
static MemoryClassLoader memory() {
static DynamicClassLoader memory() {
return CLASS_LOADER;
}
}

View File

@ -0,0 +1,24 @@
package com.flyfish.framework.compiler.core;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;
/**
* 动态类加载器
*
* @author wangyu
*/
public abstract class DynamicClassLoader extends URLClassLoader {
DynamicClassLoader() {
super(new URL[0], DynamicClassLoader.class.getClassLoader());
}
/**
* 获取类的字节码
*
* @return 结果
*/
public abstract Map<String, byte[]> getClassBytes();
}

View File

@ -1,30 +1,31 @@
package com.flyfish.framework.compiler.core;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.Closeable;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
/**
* In-memory compile Java source code as String.
*
* @author michael
*/
public class JavaStringCompiler implements Closeable {
@Slf4j
class JavaStringCompiler implements VendorJavaCompiler {
private final JavaCompiler compiler;
private final MemoryJavaFileManager manager;
private final MemoryClassLoader classLoader;
public JavaStringCompiler(MemoryClassLoader classLoader) {
JavaStringCompiler(DynamicClassLoader classLoader) {
this.compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);
this.manager = new MemoryJavaFileManager(stdManager, classLoader);
this.classLoader = classLoader;
}
/**
@ -34,27 +35,27 @@ public class JavaStringCompiler implements Closeable {
* @param source The source code as String.
* @return The compiled results as Map that contains class name as key,
* class binary as value.
* @throws IOException If compile error.
*/
public Map<String, byte[]> compile(String fileName, String source) {
@Override
public String compile(String fileName, String source) {
JavaFileObject javaFileObject = manager.makeStringSource(fileName, source);
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, Collections.singletonList(javaFileObject));
Boolean result = task.call();
if (result == null || !result) {
throw new RuntimeException("Compilation failed.");
String className = StringUtils.substringBeforeLast(fileName, ".");
if (BooleanUtils.isTrue(task.call())) {
return manager.getClassBytes().keySet().stream().filter(key -> key.endsWith(className))
.findFirst().orElse(null);
}
return manager.getClassBytes();
return null;
}
/**
* Load class from compiled classes.
* 获取动态classLoader
*
* @param name Full class name.
* @return The Class instance.
* @throws ClassNotFoundException If class not found.
* @return 结果
*/
public Class<?> loadClass(String name) throws ClassNotFoundException {
return classLoader.loadClass(name);
@Override
public DynamicClassLoader getClassLoader() {
return ClassLoaders.memory();
}
@Override
@ -63,11 +64,9 @@ public class JavaStringCompiler implements Closeable {
if (null != manager) {
manager.close();
}
if (null != classLoader) {
classLoader.close();
}
getClassLoader().close();
} catch (IOException e) {
e.printStackTrace();
log.error("关闭类编译器失败!", e);
}
}
}

View File

@ -2,8 +2,6 @@ package com.flyfish.framework.compiler.core;
import org.apache.commons.lang3.StringUtils;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -12,15 +10,11 @@ import java.util.concurrent.ConcurrentHashMap;
*
* @author michael
*/
class MemoryClassLoader extends URLClassLoader {
class MemoryClassLoader extends DynamicClassLoader {
// class name to class bytes:
private final Map<String, byte[]> classBytes = new ConcurrentHashMap<>();
public MemoryClassLoader() {
super(new URL[0], MemoryClassLoader.class.getClassLoader());
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] buf = classBytes.get(name);
@ -31,6 +25,7 @@ class MemoryClassLoader extends URLClassLoader {
return defineClass(name, buf);
}
@Override
public Map<String, byte[]> getClassBytes() {
return classBytes;
}

View File

@ -1,5 +1,7 @@
package com.flyfish.framework.compiler.core;
import lombok.Getter;
import javax.tools.*;
import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
@ -7,29 +9,24 @@ import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.nio.CharBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* In-memory java file manager.
*
* @author michael
*/
@Getter
class MemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
// compiled classes in bytes:
private final Map<String, byte[]> classBytes;
MemoryJavaFileManager(JavaFileManager fileManager, MemoryClassLoader classLoader) {
MemoryJavaFileManager(JavaFileManager fileManager, DynamicClassLoader classLoader) {
super(fileManager);
classBytes = classLoader.getClassBytes();
}
public Map<String, byte[]> getClassBytes() {
return this.classBytes;
}
@Override
public void flush() {
}

View File

@ -0,0 +1,31 @@
package com.flyfish.framework.compiler.core;
import java.io.Closeable;
/**
* 第三方java编译器
*
* @author wangyu
*/
public interface VendorJavaCompiler extends Closeable {
static VendorJavaCompiler string() {
return new JavaStringCompiler(ClassLoaders.memory());
}
/**
* 编译并返回编译结果
*
* @param name 文件名
* @param source 源码
* @return 完整类名
*/
String compile(String name, String source);
/**
* 获取动态classLoader
*
* @return 结果
*/
DynamicClassLoader getClassLoader();
}

View File

@ -1,21 +1,21 @@
package com.flyfish.framework.compiler.support;
import com.flyfish.framework.compiler.DynamicJavaCompiler;
import com.flyfish.framework.compiler.core.ClassLoaders;
import com.flyfish.framework.compiler.core.JavaStringCompiler;
import org.apache.commons.lang3.StringUtils;
import com.flyfish.framework.compiler.core.VendorJavaCompiler;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.io.IOException;
/**
* 使用第三方成熟框架
*
* @author wangyu
*/
@Slf4j
public class DelegateJavaCompiler implements DynamicJavaCompiler {
// 第三方编译器
private final JavaStringCompiler compiler = new JavaStringCompiler(ClassLoaders.memory());
private final VendorJavaCompiler compiler = VendorJavaCompiler.string();
private DelegateJavaCompiler() {
}
@ -29,22 +29,32 @@ public class DelegateJavaCompiler implements DynamicJavaCompiler {
*
* @param name 文件名
* @param source 源码
* @return 完整类名
*/
@Override
public String compile(String name, String source) {
return compiler.compile(name, source);
}
/**
* 加载类
* 用于全部编译后再加载
*
* @param name 名称
* @return 结果
*/
@Override
public Class<?> compile(String name, String source) throws ClassNotFoundException {
Map<String, byte[]> byteMap = compiler.compile(name, source);
String className = StringUtils.substringBeforeLast(name, ".");
for (String key : byteMap.keySet()) {
if (key.contains(className)) {
return compiler.loadClass(key);
public Class<?> load(String name) {
try {
return compiler.getClassLoader().loadClass(name);
} catch (ClassNotFoundException e) {
log.warn("类不存在!{}", name);
return null;
}
}
throw new RuntimeException("【java编译器】源码中没有可以编译的类");
}
@Override
public void close() {
public void close() throws IOException {
compiler.close();
}
@ -52,12 +62,4 @@ public class DelegateJavaCompiler implements DynamicJavaCompiler {
private static final DelegateJavaCompiler INSTANCE = new DelegateJavaCompiler();
}
// public static void main(String[] args) throws ClassNotFoundException, IOException {
// long time = System.currentTimeMillis();
// System.out.println(StreamUtils.copyToString(DelegateJavaCompiler.class.getResourceAsStream("./"), StandardCharsets.UTF_8));
// Class<?> clazz = getInstance().compile("Fuck.java", "package com.flyfish.project; public class Fuck {}");
// System.out.println(clazz + "耗时:" + (System.currentTimeMillis() - time) / 1000.0 + "");
// }
}

View File

@ -1,7 +1,8 @@
package com.flyfish.framework.compiler.support;
import com.flyfish.framework.compiler.DynamicJavaCompiler;
import org.springframework.util.StreamUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
@ -44,10 +45,10 @@ public class SimpleJavaCompiler implements DynamicJavaCompiler {
* @return 结果
*/
@Override
public Class<?> compile(String name, String source) throws ClassNotFoundException {
public String compile(String name, String source) {
// 存在缓存写入
if (classMap.containsKey(name)) {
return classMap.get(name);
return classMap.get(name).getName();
}
// 构建java文件对象
JavaFileObject fileObject = new StringJavaObject(name, source);
@ -55,12 +56,32 @@ public class SimpleJavaCompiler implements DynamicJavaCompiler {
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null,
options, null, Collections.singletonList(fileObject));
// 阻塞编译
if (task.call()) {
Class<?> clazz = Class.forName("com.flyfish.project." + name);
classMap.put(name, clazz);
return clazz;
if (BooleanUtils.isTrue(task.call())) {
return "com.flyfish.project." + name;
}
return null;
}
/**
* 加载类
* 用于全部编译后再加载
*
* @param name 名称
* @return 结果
*/
@Override
public Class<?> load(String name) {
String simpleName = StringUtils.substringAfterLast(name, ".");
if (classMap.containsKey(simpleName)) {
return classMap.get(simpleName);
}
try {
Class<?> clazz = Class.forName(name);
classMap.put(clazz.getSimpleName(), clazz);
return clazz;
} catch (ClassNotFoundException e) {
return null;
}
throw new RuntimeException("【java编译器】尝试编译源代码出现异常");
}
@Override

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>flyfish-framework</artifactId>
<groupId>com.flyfish.framework</groupId>
<version>0.0.1-SNAPSHOT</version>
<version>0.0.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>flyfish-framework</artifactId>
<groupId>com.flyfish.framework</groupId>
<version>0.0.1-SNAPSHOT</version>
<version>0.0.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>flyfish-framework</artifactId>
<groupId>com.flyfish.framework</groupId>
<version>0.0.1-SNAPSHOT</version>
<version>0.0.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>flyfish-framework</artifactId>
<groupId>com.flyfish.framework</groupId>
<version>0.0.1-SNAPSHOT</version>
<version>0.0.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>flyfish-framework</artifactId>
<groupId>com.flyfish.framework</groupId>
<version>0.0.1-SNAPSHOT</version>
<version>0.0.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>flyfish-framework</artifactId>
<groupId>com.flyfish.framework</groupId>
<version>0.0.1-SNAPSHOT</version>
<version>0.0.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>flyfish-framework</artifactId>
<groupId>com.flyfish.framework</groupId>
<version>0.0.1-SNAPSHOT</version>
<version>0.0.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -46,6 +46,7 @@ class RepositoryRegistrarComposite implements RepositoryRegistrar {
@Override
public void register(Class<?> clazz) {
if (null == clazz) return;
registrars.forEach(registrar -> registrar.register(clazz));
}
}

View File

@ -83,11 +83,37 @@ public class RestBeanAutoConfigure implements ImportBeanDefinitionRegistrar, Res
// repo注册器
RepositoryRegistrar repositoryRegistrar = RepositoryRegistrar.newInstance(metadata)
.init(metadata, resourceLoader, environment, registry, beanNameGenerator);
// 准备注册的repo
Map<RestBeanCandidate, List<Class<?>>> loadedClasses = new ConcurrentHashMap<>();
// 寻找基本包路径
String basePackage = basePackages.stream().findFirst().orElse("com.flyfish.project");
// 获取被注解的类开始编译
Set<Class<?>> classes = reflections.getTypesAnnotatedWith(RestBean.class);
// 并发编译快速接入
// 并发编译快速接入准备注册的repo
Map<RestBeanCandidate, List<String>> compiledClasses = compile(basePackage, classes);
// 从repo开始注册bean
compiledClasses.getOrDefault(REPOSITORY, Collections.emptyList())
.stream()
.map(compiler::load)
.forEach(repositoryRegistrar::register);
compiledClasses.getOrDefault(SERVICE, Collections.emptyList())
.forEach(clazz -> registerBean(clazz, registry));
compiledClasses.getOrDefault(CONTROLLER, Collections.emptyList())
.forEach(clazz -> registerBean(clazz, registry));
compiledClasses.getOrDefault(VIEW_CONTROLLER, Collections.emptyList())
.forEach(clazz -> registerBean(clazz, registry));
compiledClasses.getOrDefault(REACTIVE_VIEW_CONTROLLER, Collections.emptyList())
.forEach(clazz -> registerBean(clazz, registry));
log.info("结束注册rest bean...");
}
/**
* 解析并编译带注解的rest bean
*
* @param basePackage 基本路径
* @param classes 类集合
* @return 结果
*/
private Map<RestBeanCandidate, List<String>> compile(String basePackage, Set<Class<?>> classes) {
Map<RestBeanCandidate, List<String>> compiled = new ConcurrentHashMap<>();
classes.parallelStream().forEach(clazz -> {
RestBean restBean = clazz.getDeclaredAnnotation(RestBean.class);
if (null != restBean) {
@ -100,7 +126,7 @@ public class RestBeanAutoConfigure implements ImportBeanDefinitionRegistrar, Res
.setBeanClass(clazz.getCanonicalName())
.setQueryBeanClass(restBean.queryClass().getCanonicalName())
.setListViewClass(hasVo ? restBean.listViewClass().getCanonicalName() : null)
.setPackageName(basePackages.stream().findFirst().orElse("com.flyfish.project"))
.setPackageName(basePackage)
.setUri(makePath(clazz.getSimpleName(), restBean.value()));
// 分别生成实现类从repo到controller
templates.forEach((type, template) -> {
@ -111,37 +137,30 @@ public class RestBeanAutoConfigure implements ImportBeanDefinitionRegistrar, Res
source.setSuperClass(superClasses.getOrDefault(type, noOp).apply(restBean).getCanonicalName());
source.setClassName(clazz.getSimpleName() + type.getName());
try {
log.info("尝试注册{}", source.getClassName());
Class<?> compiled = compiler.compile(source.getClassName() + ".java", template.apply(source));
loadedClasses.computeIfAbsent(type, k -> new ArrayList<>()).add(compiled);
// IllegalAccessException | InvocationTargetException
} catch (ClassNotFoundException | IOException e) {
log.error("注册{}时发生异常!", source.getClassName(), e);
log.info("尝试编译{}", source.getClassName());
String className = compiler.compile(source.getClassName() + ".java", template.apply(source));
Assert.hasText(className, "【java编译器】源码中没有可以编译的类");
compiled.computeIfAbsent(type, k -> new ArrayList<>()).add(className);
} catch (IOException e) {
log.error("编译{}时发生异常!", source.getClassName(), e);
} catch (Exception e) {
log.error("编译{}警告!{}", source.getClassName(), e.getMessage());
}
});
}
});
// 从repo开始注册bean
loadedClasses.getOrDefault(REPOSITORY, Collections.emptyList())
.forEach(repositoryRegistrar::register);
loadedClasses.getOrDefault(SERVICE, Collections.emptyList())
.forEach(clazz -> registerBean(clazz, registry));
loadedClasses.getOrDefault(CONTROLLER, Collections.emptyList())
.forEach(clazz -> registerBean(clazz, registry));
loadedClasses.getOrDefault(VIEW_CONTROLLER, Collections.emptyList())
.forEach(clazz -> registerBean(clazz, registry));
loadedClasses.getOrDefault(REACTIVE_VIEW_CONTROLLER, Collections.emptyList())
.forEach(clazz -> registerBean(clazz, registry));
log.info("结束注册rest bean...");
return compiled;
}
/**
* 注册bean如果是controller自动注册controller
*
* @param clazz class要注册的class
* @param className class要注册的class
* @param registry 注册器注册bean用
*/
private void registerBean(Class<?> clazz, BeanDefinitionRegistry registry) {
private void registerBean(String className, BeanDefinitionRegistry registry) {
Class<?> clazz = compiler.load(className);
Assert.notNull(clazz, "类加载失败:" + className);
//生成BeanDefinition并注册到容器中
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
BeanDefinition beanDefinition = builder.getRawBeanDefinition();

View File

@ -6,13 +6,13 @@
<groupId>com.flyfish.framework</groupId>
<artifactId>flyfish-framework</artifactId>
<version>0.0.1-SNAPSHOT</version>
<version>0.0.2-SNAPSHOT</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.11</version>
<version>2.7.14</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>