diff --git a/flyfish-common/pom.xml b/flyfish-common/pom.xml index ecb9225..934eb11 100644 --- a/flyfish-common/pom.xml +++ b/flyfish-common/pom.xml @@ -59,10 +59,6 @@ org.slf4j slf4j-api - - com.itranswarp - compiler - org.reflections reflections diff --git a/flyfish-common/src/main/java/com/flyfish/framework/compiler/DynamicJavaCompiler.java b/flyfish-common/src/main/java/com/flyfish/framework/compiler/DynamicJavaCompiler.java index 0f8ffb1..ed3d605 100644 --- a/flyfish-common/src/main/java/com/flyfish/framework/compiler/DynamicJavaCompiler.java +++ b/flyfish-common/src/main/java/com/flyfish/framework/compiler/DynamicJavaCompiler.java @@ -3,6 +3,7 @@ package com.flyfish.framework.compiler; import com.flyfish.framework.compiler.support.DelegateJavaCompiler; import com.flyfish.framework.compiler.support.SimpleJavaCompiler; +import java.io.Closeable; import java.io.IOException; /** @@ -11,10 +12,11 @@ import java.io.IOException; * * @author wangyu */ -public interface DynamicJavaCompiler { +public interface DynamicJavaCompiler extends Closeable { /** * 代理的java编译器,使用库文件 + * * @return 结果 */ static DynamicJavaCompiler delegate() { diff --git a/flyfish-common/src/main/java/com/flyfish/framework/compiler/core/JavaStringCompiler.java b/flyfish-common/src/main/java/com/flyfish/framework/compiler/core/JavaStringCompiler.java new file mode 100644 index 0000000..5ca66ec --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/compiler/core/JavaStringCompiler.java @@ -0,0 +1,82 @@ +package com.flyfish.framework.compiler.core; + +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 { + + private final JavaCompiler compiler; + private final MemoryJavaFileManager manager; + private final MemoryClassLoader classLoader; + + public JavaStringCompiler() { + this.compiler = ToolProvider.getSystemJavaCompiler(); + StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null); + this.manager = new MemoryJavaFileManager(stdManager); + this.classLoader = new MemoryClassLoader(manager.getClassBytes()); + } + + /** + * Compile a Java source file in memory. + * + * @param fileName Java file name, e.g. "Test.java" + * @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 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."); + } + return manager.getClassBytes(); + } + + /** + * Load class from compiled classes. + * + * @param name Full class name. + * @return The Class instance. + * @throws ClassNotFoundException If class not found. + */ + public Class loadClass(String name) throws ClassNotFoundException { + return classLoader.loadClass(name); + } + + /** + * get the classLoader + * + * @return 结果 + */ + public ClassLoader getClassLoader() { + return classLoader; + } + + @Override + public void close() { + try { + if (null != manager) { + manager.close(); + } + if (null != classLoader) { + classLoader.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/compiler/core/MemoryClassLoader.java b/flyfish-common/src/main/java/com/flyfish/framework/compiler/core/MemoryClassLoader.java new file mode 100644 index 0000000..06eea9a --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/compiler/core/MemoryClassLoader.java @@ -0,0 +1,32 @@ +package com.flyfish.framework.compiler.core; + +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Map; + +/** + * Load class from byte[] which is compiled in memory. + * + * @author michael + */ +class MemoryClassLoader extends URLClassLoader { + + // class name to class bytes: + private final Map classBytes; + + public MemoryClassLoader(Map classBytes) { + super(new URL[0], MemoryClassLoader.class.getClassLoader()); + this.classBytes = classBytes; + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + byte[] buf = classBytes.get(name); + if (buf == null) { + return super.findClass(name); + } + classBytes.remove(name); + return defineClass(name, buf, 0, buf.length); + } + +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/compiler/core/MemoryJavaFileManager.java b/flyfish-common/src/main/java/com/flyfish/framework/compiler/core/MemoryJavaFileManager.java new file mode 100644 index 0000000..60887dd --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/compiler/core/MemoryJavaFileManager.java @@ -0,0 +1,91 @@ +package com.flyfish.framework.compiler.core; + +import javax.tools.*; +import java.io.ByteArrayOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.nio.CharBuffer; +import java.util.HashMap; +import java.util.Map; + +/** + * In-memory java file manager. + * + * @author michael + */ +class MemoryJavaFileManager extends ForwardingJavaFileManager { + + // compiled classes in bytes: + final Map classBytes = new HashMap<>(); + + MemoryJavaFileManager(JavaFileManager fileManager) { + super(fileManager); + } + + public Map getClassBytes() { + return this.classBytes; + } + + @Override + public void flush() { + } + + @Override + public void close() throws IOException { + classBytes.clear(); + } + + @Override + public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, + FileObject sibling) throws IOException { + if (kind == JavaFileObject.Kind.CLASS) { + return new MemoryOutputJavaFileObject(className); + } else { + return super.getJavaFileForOutput(location, className, kind, sibling); + } + } + + JavaFileObject makeStringSource(String name, String code) { + return new MemoryInputJavaFileObject(name, code); + } + + static class MemoryInputJavaFileObject extends SimpleJavaFileObject { + + final String code; + + MemoryInputJavaFileObject(String name, String code) { + super(URI.create("string:///" + name), Kind.SOURCE); + this.code = code; + } + + @Override + public CharBuffer getCharContent(boolean ignoreEncodingErrors) { + return CharBuffer.wrap(code); + } + } + + class MemoryOutputJavaFileObject extends SimpleJavaFileObject { + final String name; + + MemoryOutputJavaFileObject(String name) { + super(URI.create("string:///" + name), Kind.CLASS); + this.name = name; + } + + @Override + public OutputStream openOutputStream() { + return new FilterOutputStream(new ByteArrayOutputStream()) { + @Override + public void close() throws IOException { + out.close(); + ByteArrayOutputStream bos = (ByteArrayOutputStream) out; + classBytes.put(name, bos.toByteArray()); + } + }; + } + + } + +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/DelegateJavaCompiler.java b/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/DelegateJavaCompiler.java index d363feb..7eb6ccb 100644 --- a/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/DelegateJavaCompiler.java +++ b/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/DelegateJavaCompiler.java @@ -1,7 +1,7 @@ package com.flyfish.framework.compiler.support; import com.flyfish.framework.compiler.DynamicJavaCompiler; -import com.itranswarp.compiler.JavaStringCompiler; +import com.flyfish.framework.compiler.core.JavaStringCompiler; import org.apache.commons.lang3.StringUtils; import org.springframework.util.StreamUtils; @@ -19,7 +19,6 @@ public class DelegateJavaCompiler implements DynamicJavaCompiler { // 第三方编译器 private final JavaStringCompiler compiler = new JavaStringCompiler(); - private DelegateJavaCompiler() { } @@ -27,7 +26,9 @@ public class DelegateJavaCompiler implements DynamicJavaCompiler { return SingletonHolder.INSTANCE; } - + public static ClassLoader classLoader() { + return getInstance().compiler.getClassLoader(); + } /** * 编译java源码,通过字符串 @@ -37,22 +38,26 @@ public class DelegateJavaCompiler implements DynamicJavaCompiler { * @return 结果 */ @Override - public Class compile(String name, String source) throws IOException, ClassNotFoundException { + public Class compile(String name, String source) throws ClassNotFoundException { Map byteMap = compiler.compile(name, source); String className = StringUtils.substringBeforeLast(name, "."); for (String key : byteMap.keySet()) { if (key.contains(className)) { - return compiler.loadClass(key, byteMap); + return compiler.loadClass(key); } } throw new RuntimeException("【java编译器】源码中没有可以编译的类!"); } + @Override + public void close() { + compiler.close(); + } + private static class SingletonHolder { private static final DelegateJavaCompiler INSTANCE = new DelegateJavaCompiler(); - } diff --git a/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/SimpleJavaCompiler.java b/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/SimpleJavaCompiler.java index 832b498..21c3973 100644 --- a/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/SimpleJavaCompiler.java +++ b/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/SimpleJavaCompiler.java @@ -63,6 +63,11 @@ public class SimpleJavaCompiler implements DynamicJavaCompiler { throw new RuntimeException("【java编译器】尝试编译源代码出现异常!"); } + @Override + public void close() throws IOException { + + } + private static class SingletonHolder { private static final SimpleJavaCompiler INSTANCE = new SimpleJavaCompiler(); diff --git a/flyfish-web/src/main/java/com/flyfish/framework/beans/repository/CustomRepositoryRegistrar.java b/flyfish-web/src/main/java/com/flyfish/framework/beans/repository/CustomRepositoryRegistrar.java index b532e6f..b9a201e 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/beans/repository/CustomRepositoryRegistrar.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/beans/repository/CustomRepositoryRegistrar.java @@ -32,6 +32,7 @@ public class CustomRepositoryRegistrar { // 构建器 this.builder = new CustomRepositoryBuilder(registry, extension, configurationSource, resourceLoader, environment); + } /** @@ -50,6 +51,7 @@ public class CustomRepositoryRegistrar { extension.postProcess(definitionBuilder, configurationSource); extension.postProcess(definitionBuilder, (AnnotationRepositoryConfigurationSource) configurationSource); + AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition(); beanDefinition.setPrimary(configuration.isPrimary()); String beanName = StringUtils.uncapitalize(clazz.getSimpleName()); diff --git a/flyfish-web/src/main/java/com/flyfish/framework/beans/resolver/DynamicRestBeanResolver.java b/flyfish-web/src/main/java/com/flyfish/framework/beans/resolver/DynamicRestBeanResolver.java index 54bfcd5..db770c9 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/beans/resolver/DynamicRestBeanResolver.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/beans/resolver/DynamicRestBeanResolver.java @@ -1,10 +1,52 @@ package com.flyfish.framework.beans.resolver; +import com.flyfish.framework.compiler.DynamicJavaCompiler; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.annotation.Lazy; +import org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping; +import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + /** * 动态Bean生成解析器 * * @author wangyu */ -public class DynamicRestBeanResolver { +@Slf4j +@Lazy(false) +public class DynamicRestBeanResolver implements InitializingBean, DisposableBean { + private static final List controllers = new ArrayList<>(); + private final Method handlerRegister; + private final RequestMappingHandlerMapping mapping; + + public DynamicRestBeanResolver(RequestMappingHandlerMapping mapping) throws NoSuchMethodException { + this.mapping = mapping; + this.handlerRegister = AbstractHandlerMethodMapping.class.getDeclaredMethod("detectHandlerMethods", Object.class); + } + + public static void addController(String controller) { + controllers.add(controller); + } + + @Override + public void destroy() throws Exception { + log.info("正在销毁rest自动配置器"); + DynamicJavaCompiler.delegate().close(); + } + + @Override + public void afterPropertiesSet() throws Exception { + log.info("正在注册controller..."); + for (String bean : controllers) { + log.info("注册controller:{}", bean); + handlerRegister.setAccessible(true); + handlerRegister.invoke(mapping, bean); + } + } } diff --git a/flyfish-web/src/main/java/com/flyfish/framework/boot/FlyfishAppRunner.java b/flyfish-web/src/main/java/com/flyfish/framework/boot/FlyfishAppRunner.java new file mode 100644 index 0000000..9a4722f --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/boot/FlyfishAppRunner.java @@ -0,0 +1,21 @@ +package com.flyfish.framework.boot; + + +import com.flyfish.framework.compiler.support.DelegateJavaCompiler; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * 启动类包装 + * + * @author wangyu + * 实现了自定义的类加载器 + */ +public class FlyfishAppRunner { + + public static ConfigurableApplicationContext run(Class primarySource, String... args) { + Thread.currentThread().setContextClassLoader(DelegateJavaCompiler.classLoader()); + return SpringApplication.run(new Class[]{primarySource}, args); + } + +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/config/BeanConfig.java b/flyfish-web/src/main/java/com/flyfish/framework/config/BeanConfig.java index 70c30d8..9edb572 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/config/BeanConfig.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/config/BeanConfig.java @@ -1,10 +1,14 @@ package com.flyfish.framework.config; +import com.flyfish.framework.beans.resolver.DynamicRestBeanResolver; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.annotation.Bean; import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.server.WebFilter; +import java.util.ArrayList; + /** * 默认初始化的Bean配置 * @@ -33,4 +37,11 @@ public class BeanConfig { }; } + + @Bean + public DynamicRestBeanResolver dynamicRestBeanResolver(RequestMappingHandlerMapping requestMappingHandlerMapping) + throws NoSuchMethodException { + return new DynamicRestBeanResolver(requestMappingHandlerMapping); + } + } diff --git a/flyfish-web/src/main/java/com/flyfish/framework/config/RestBeanAutoConfigure.java b/flyfish-web/src/main/java/com/flyfish/framework/config/RestBeanAutoConfigure.java index 853a124..e2be5e7 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/config/RestBeanAutoConfigure.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/config/RestBeanAutoConfigure.java @@ -3,6 +3,7 @@ package com.flyfish.framework.config; import com.flyfish.framework.beans.annotations.EnableRestBeanDetect; import com.flyfish.framework.beans.annotations.RestBean; import com.flyfish.framework.beans.repository.CustomRepositoryRegistrar; +import com.flyfish.framework.beans.resolver.DynamicRestBeanResolver; import com.flyfish.framework.compiler.DynamicJavaCompiler; import com.flyfish.framework.compiler.support.JavaSource; import com.flyfish.framework.compiler.template.TemplateCompiler; @@ -11,7 +12,6 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.reflections.Reflections; -import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; @@ -22,13 +22,8 @@ import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.AnnotationMetadata; -import org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping; -import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping; -import javax.annotation.Resource; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; @@ -41,24 +36,19 @@ import java.util.stream.Stream; * @author wangyu */ @Slf4j -public class RestBeanAutoConfigure implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware, InitializingBean { +public class RestBeanAutoConfigure implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { // 预编译模板 private final Map> templates = Stream.of("Repository", "Service", "Controller") .collect(Collectors.toMap(key -> key, filename -> TemplateCompiler.compile("/templates/" + filename + ".tpl"))); private final Map>> superClasses; - private final Method handlerRegister; - private final List controllers = new ArrayList<>(); - @Resource - private RequestMappingHandlerMapping requestMappingHandlerMapping; // 资源加载器 private ResourceLoader resourceLoader; // 环境变量 private Environment environment; - public RestBeanAutoConfigure() throws NoSuchMethodException { - this.handlerRegister = AbstractHandlerMethodMapping.class.getDeclaredMethod("detectHandlerMethods", Object.class); + public RestBeanAutoConfigure() { superClasses = new HashMap<>(); superClasses.put("Controller", RestBean::controllerClass); superClasses.put("Repository", RestBean::repoClass); @@ -138,7 +128,7 @@ public class RestBeanAutoConfigure implements ImportBeanDefinitionRegistrar, Res registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); // 如果是controller,注册 if (clazz.getSimpleName().endsWith("Controller")) { - controllers.add(beanName); + DynamicRestBeanResolver.addController(beanName); } } @@ -160,15 +150,6 @@ public class RestBeanAutoConfigure implements ImportBeanDefinitionRegistrar, Res } } - @Override - public void afterPropertiesSet() throws Exception { - for (String bean : controllers) { - log.info("注册controller:{}", bean); - handlerRegister.setAccessible(true); - handlerRegister.invoke(requestMappingHandlerMapping, bean); - } - } - @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; diff --git a/pom.xml b/pom.xml index 040d867..2125fd4 100644 --- a/pom.xml +++ b/pom.xml @@ -84,11 +84,6 @@ commons-io 2.6 - - com.itranswarp - compiler - 1.0 - org.reflections reflections