diff --git a/flyfish-common/pom.xml b/flyfish-common/pom.xml index ae28dd3..ecb9225 100644 --- a/flyfish-common/pom.xml +++ b/flyfish-common/pom.xml @@ -59,6 +59,14 @@ org.slf4j slf4j-api + + com.itranswarp + compiler + + + org.reflections + reflections + \ No newline at end of file diff --git a/flyfish-common/src/main/java/com/flyfish/framework/bean/EnumValue.java b/flyfish-common/src/main/java/com/flyfish/framework/bean/EnumValue.java new file mode 100644 index 0000000..d60fdea --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/bean/EnumValue.java @@ -0,0 +1,19 @@ +package com.flyfish.framework.bean; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用来表示枚举 + * @author wangyu + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EnumValue { + + private String value; + + private String text; +} 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 new file mode 100644 index 0000000..0f8ffb1 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/compiler/DynamicJavaCompiler.java @@ -0,0 +1,41 @@ +package com.flyfish.framework.compiler; + +import com.flyfish.framework.compiler.support.DelegateJavaCompiler; +import com.flyfish.framework.compiler.support.SimpleJavaCompiler; + +import java.io.IOException; + +/** + * 动态的java编译器 + * 提供动态的java编译 + * + * @author wangyu + */ +public interface DynamicJavaCompiler { + + /** + * 代理的java编译器,使用库文件 + * @return 结果 + */ + static DynamicJavaCompiler delegate() { + return DelegateJavaCompiler.getInstance(); + } + + /** + * 简单的java编译器 + * + * @return 结果 + */ + static DynamicJavaCompiler simple() { + return SimpleJavaCompiler.getInstance(); + } + + /** + * 编译java源码,通过字符串 + * + * @param name 文件名 + * @param source 源码 + * @return 结果 + */ + Class compile(String name, String source) throws ClassNotFoundException, IOException; +} 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 new file mode 100644 index 0000000..d363feb --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/DelegateJavaCompiler.java @@ -0,0 +1,65 @@ +package com.flyfish.framework.compiler.support; + +import com.flyfish.framework.compiler.DynamicJavaCompiler; +import com.itranswarp.compiler.JavaStringCompiler; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.StreamUtils; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +/** + * 使用第三方成熟框架 + * + * @author wangyu + */ +public class DelegateJavaCompiler implements DynamicJavaCompiler { + + // 第三方编译器 + private final JavaStringCompiler compiler = new JavaStringCompiler(); + + + private DelegateJavaCompiler() { + } + + public static DelegateJavaCompiler getInstance() { + return SingletonHolder.INSTANCE; + } + + + + /** + * 编译java源码,通过字符串 + * + * @param name 文件名 + * @param source 源码 + * @return 结果 + */ + @Override + public Class compile(String name, String source) throws IOException, 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); + } + } + throw new RuntimeException("【java编译器】源码中没有可以编译的类!"); + } + + + private static class SingletonHolder { + + 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 + "秒"); + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/JavaSource.java b/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/JavaSource.java new file mode 100644 index 0000000..ed9fbe4 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/JavaSource.java @@ -0,0 +1,50 @@ +package com.flyfish.framework.compiler.support; + +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import org.apache.commons.lang3.StringUtils; + +/** + * java源代码对象,可以继承进行扩展 + * @author wangyu + */ +@Data +@NoArgsConstructor(staticName = "create") +@Accessors(chain = true) +public class JavaSource { + + // 类名 + private String className; + + // 源码 + private String source; + + // 包名 + private String packageName; + + // 父类名 + private String superClass; + + // bean的class + private String beanClass; + + // 查询实体class + private String queryBeanClass; + + // controller需要,提供请求地址 + private String uri; + + public String getSuperClassName() { + return StringUtils.substringAfterLast(superClass, "."); + } + + public String getBeanClassName() { + return StringUtils.substringAfterLast(beanClass, "."); + } + + public String getQueryBeanClassName() { + return StringUtils.substringAfterLast(queryBeanClass, "."); + } +} 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 new file mode 100644 index 0000000..832b498 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/SimpleJavaCompiler.java @@ -0,0 +1,71 @@ +package com.flyfish.framework.compiler.support; + +import com.flyfish.framework.compiler.DynamicJavaCompiler; +import org.springframework.util.StreamUtils; + +import javax.tools.JavaCompiler; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.ToolProvider; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 简单的java编译器,单文件,无缓存版 + * 只为了偷懒,快速撸代码 + * + * @author wangyu + */ +public class SimpleJavaCompiler implements DynamicJavaCompiler { + + // 当前的java编译器 + private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + // java文件管理器 + private final JavaFileManager fileManager = compiler.getStandardFileManager(null, Locale.SIMPLIFIED_CHINESE, StandardCharsets.UTF_8); + // 编译参数 + private final List options = Arrays.asList("-d", "./bin"); + // 编译池 + private final Map> classMap = new ConcurrentHashMap<>(); + + private SimpleJavaCompiler() { + } + + public static SimpleJavaCompiler getInstance() { + return SingletonHolder.INSTANCE; + } + + /** + * 编译java源码,通过字符串 + * + * @param name 文件名R + * @param source 源码 + * @return 结果 + */ + @Override + public Class compile(String name, String source) throws ClassNotFoundException { + // 存在缓存,写入 + if (classMap.containsKey(name)) { + return classMap.get(name); + } + // 构建java文件对象 + JavaFileObject fileObject = new StringJavaObject(name, source); + // 设置编译环境 + 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; + } + throw new RuntimeException("【java编译器】尝试编译源代码出现异常!"); + } + + private static class SingletonHolder { + + private static final SimpleJavaCompiler INSTANCE = new SimpleJavaCompiler(); + + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/StringJavaObject.java b/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/StringJavaObject.java new file mode 100644 index 0000000..5465ad8 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/compiler/support/StringJavaObject.java @@ -0,0 +1,35 @@ +package com.flyfish.framework.compiler.support; + +import javax.tools.SimpleJavaFileObject; +import java.io.IOException; +import java.net.URI; + +/** + * 以文本存在的java对象 + * + * @author wangyu + */ +public class StringJavaObject extends SimpleJavaFileObject { + + private final String content; + + public StringJavaObject(String filename, String content) { + super(stringJavaObjectUri(filename), Kind.SOURCE); + this.content = content; + } + + /** + * 字符串java对象uri + * + * @param name 文件名 + * @return 结果 + */ + private static URI stringJavaObjectUri(String name) { + return URI.create("string:///" + name + Kind.SOURCE.extension); + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return content; + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/compiler/template/TemplateCompiler.java b/flyfish-common/src/main/java/com/flyfish/framework/compiler/template/TemplateCompiler.java new file mode 100644 index 0000000..0642e3a --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/compiler/template/TemplateCompiler.java @@ -0,0 +1,88 @@ +package com.flyfish.framework.compiler.template; + +import com.flyfish.framework.compiler.support.JavaSource; +import org.springframework.beans.BeanUtils; +import org.springframework.data.util.CastUtils; +import org.springframework.util.StreamUtils; + +import java.beans.PropertyDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 模板编译器 + * + * @author wangyu + */ +public class TemplateCompiler { + + /** + * 编译模板文件和源码数据为源代码字符串 + * + * @param dataClass 数据class + * @param filename 文件名 + * @return 结果 + */ + public static Function compile(Class dataClass, String filename) { + // 获取文件内容 + InputStream fis = TemplateCompiler.class.getResourceAsStream(filename); + try { + // 获取模板内容 + String template = StreamUtils.copyToString(fis, StandardCharsets.UTF_8); + // 准备变量池 + Map pool = Arrays.stream(BeanUtils.getPropertyDescriptors(dataClass)) + .collect(Collectors.toMap(descriptor -> "#{" + descriptor.getName() + "}", a -> a)); + // 编译 + return cfg -> pool.keySet().stream().reduce(template, (result, key) -> { + // 获取读取方法 + Method method = pool.get(key).getReadMethod(); + Object value = null; + try { + // 取得结果 + value = method.invoke(cfg); + } catch (Exception ignored) { + } + // 替换变量 + return result.replace(key, parseValue(value)); + }, (a, b) -> a); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("模板编译失败!"); + } + } + + /** + * 解析单个值 + * + * @param value 值 + * @return 结果 + */ + private static String parseValue(Object value) { + if (null == value) { + return ""; + } + if (value instanceof Map) { + Map map = CastUtils.cast(value); + return map.entrySet().stream() + .map(entry -> entry.getKey() + " " + entry.getValue()) + .collect(Collectors.joining(" ")); + } + return String.valueOf(value); + } + + /** + * 编译模板文件和源码数据为源代码字符串 + * + * @param filename 文件名 + * @return 结果 + */ + public static Function compile(String filename) { + return compile(JavaSource.class, filename); + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/utils/MapBuilder.java b/flyfish-common/src/main/java/com/flyfish/framework/utils/MapBuilder.java new file mode 100644 index 0000000..2cc4b8e --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/utils/MapBuilder.java @@ -0,0 +1,47 @@ +package com.flyfish.framework.utils; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * map构造器 + * + * @author wangyu + */ +public class MapBuilder { + + // 内部的map + private final Map map = new LinkedHashMap<>(); + + /** + * 构造器 + * + * @param 键 + * @param 值 + * @return 结果 + */ + public static MapBuilder builder() { + return new MapBuilder<>(); + } + + /** + * 放置参数 + * + * @param k 键 + * @param v 值 + * @return 结果 + */ + public MapBuilder put(K k, V v) { + this.map.put(k, v); + return this; + } + + /** + * 构建 + * + * @return 结果 + */ + public Map build() { + return map; + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/utils/StringFormats.java b/flyfish-common/src/main/java/com/flyfish/framework/utils/StringFormats.java new file mode 100644 index 0000000..7ce4ba7 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/utils/StringFormats.java @@ -0,0 +1,37 @@ +package com.flyfish.framework.utils; + +import org.apache.commons.lang3.StringUtils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 字符串格式化工具类 + * + * @author wangyu + */ +public abstract class StringFormats { + + // 匹配表达式 + private static final Pattern pattern = Pattern.compile("[A-Z]([a-z\\d]+)?"); + + /** + * 驼峰转连字符 + * + * @param value 字符串 + * @return 结果 + */ + public static String camel2Line(String value) { + if (StringUtils.isBlank(value)) { + return value; + } + StringBuilder sb = new StringBuilder(); + Matcher matcher = pattern.matcher(value); + while (matcher.find()) { + String word = matcher.group(); + sb.append(word.toLowerCase()); + sb.append(matcher.end() == value.length() ? "" : "-"); + } + return sb.toString(); + } +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/annotations/EnableEnumEndpoint.java b/flyfish-data/src/main/java/com/flyfish/framework/annotations/EnableEnumEndpoint.java new file mode 100644 index 0000000..aa028a6 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/annotations/EnableEnumEndpoint.java @@ -0,0 +1,25 @@ +package com.flyfish.framework.annotations; + +import com.flyfish.framework.config.EnumConfig; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +/** + * 启用枚举端点,处理枚举类型 + * + * @author wangyu + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(EnumConfig.class) +public @interface EnableEnumEndpoint { + + /** + * 默认扫描包 + * + * @return 结果 + */ + String[] scanPackages() default "com.flyfish.project"; +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/config/EnumConfig.java b/flyfish-data/src/main/java/com/flyfish/framework/config/EnumConfig.java new file mode 100644 index 0000000..2a4cb2b --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/config/EnumConfig.java @@ -0,0 +1,45 @@ +package com.flyfish.framework.config; + +import com.flyfish.framework.bean.EnumValue; +import com.flyfish.framework.enums.NamedEnum; +import com.flyfish.framework.utils.StringFormats; +import org.apache.commons.lang3.ClassUtils; +import org.reflections.Reflections; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 枚举相关,专门处理枚举 + * @author wangyu + */ +public class EnumConfig implements InitializingBean, ImportBeanDefinitionRegistrar { + + // 映射管理,避免class.forName扫描包路径 + private final Map> mapper = new HashMap<>(); + + private final Map> values = new HashMap<>(); + + public EnumConfig() { + Reflections reflections = new Reflections("com.flyfish.project"); + // 得到Resource注解的类 + Set> classSet = reflections.getSubTypesOf(NamedEnum.class); + // 注入 + classSet.stream().filter(clazz -> ClassUtils.isAssignable(clazz, Enum.class)).forEach(clazz -> { + String name = StringFormats.camel2Line(clazz.getSimpleName()); + List values = Arrays.stream(clazz.getEnumConstants()).reduce(new ArrayList<>(), (result, item) -> { + result.add(new EnumValue(((Enum) item).name(), item.getName())); + return result; + }, (a, b) -> a); + this.mapper.put(name, values.stream().collect(Collectors.toMap(EnumValue::getValue, EnumValue::getText))); + this.values.put(name, values); + }); + } + + @Override + public void afterPropertiesSet() throws Exception { + + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/beans/annotations/EnableRestBeanDetect.java b/flyfish-web/src/main/java/com/flyfish/framework/beans/annotations/EnableRestBeanDetect.java new file mode 100644 index 0000000..9e56d1a --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/beans/annotations/EnableRestBeanDetect.java @@ -0,0 +1,23 @@ +package com.flyfish.framework.beans.annotations; + +import com.flyfish.framework.config.RestBeanAutoConfigure; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.*; + +/** + * 启动rest bean的扫描 + * @author wangyu + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(RestBeanAutoConfigure.class) +public @interface EnableRestBeanDetect { + + /** + * 扫描的基本路径 + * @return 结果 + */ + String[] basePackages() default "com.flyfish.project"; +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/beans/annotations/RestBean.java b/flyfish-web/src/main/java/com/flyfish/framework/beans/annotations/RestBean.java index 8f7cbff..3395b3f 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/beans/annotations/RestBean.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/beans/annotations/RestBean.java @@ -1,5 +1,12 @@ package com.flyfish.framework.beans.annotations; +import com.flyfish.framework.controller.BaseController; +import com.flyfish.framework.controller.SafeController; +import com.flyfish.framework.domain.base.Qo; +import com.flyfish.framework.repository.DefaultRepository; +import com.flyfish.framework.service.BaseService; +import com.flyfish.framework.service.impl.BaseServiceImpl; + import java.lang.annotation.*; /** @@ -15,7 +22,34 @@ public @interface RestBean { /** * 生成的REST资源名称 * - * @return + * @return 资源名称 */ String value() default ""; + + /** + * 必须指定qo + * @return 结果 + */ + Class> queryClass(); + + /** + * 仓库的超类,默认是default + * + * @return 结果 + */ + Class repoClass() default DefaultRepository.class; + + /** + * 服务类,动态生成服务 + * + * @return 结果 + */ + Class serviceClass() default BaseServiceImpl.class; + + /** + * controller的类,支持自定义 + * + * @return 结果 + */ + Class controllerClass() default BaseController.class; } diff --git a/flyfish-web/src/main/java/com/flyfish/framework/beans/repository/CustomRepositoryBuilder.java b/flyfish-web/src/main/java/com/flyfish/framework/beans/repository/CustomRepositoryBuilder.java new file mode 100644 index 0000000..915c24d --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/beans/repository/CustomRepositoryBuilder.java @@ -0,0 +1,220 @@ +package com.flyfish.framework.beans.repository; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.data.config.ParsingUtils; +import org.springframework.data.repository.config.*; +import org.springframework.data.repository.core.support.RepositoryFragment; +import org.springframework.data.repository.core.support.RepositoryFragmentsFactoryBean; +import org.springframework.data.util.Optionals; +import org.springframework.util.Assert; + +/** + * Builder to create {@link BeanDefinitionBuilder} instance to eventually create Spring Data repository instances. + * + * @author Oliver Gierke + * @author Christoph Strobl + * @author Peter Rietzler + * @author Mark Paluch + */ +public class CustomRepositoryBuilder { + + private static final Logger LOGGER = LoggerFactory.getLogger(CustomRepositoryBuilder.class); + + private final BeanDefinitionRegistry registry; + private final RepositoryConfigurationExtension extension; + private final ResourceLoader resourceLoader; + + private final MetadataReaderFactory metadataReaderFactory; + private final FragmentMetadata fragmentMetadata; + private final CustomRepositoryImplementationDetector implementationDetector; + + /** + * Creates a new {@link CustomRepositoryBuilder} from the given {@link BeanDefinitionRegistry}, + * {@link RepositoryConfigurationExtension} and {@link ResourceLoader}. + * + * @param registry must not be {@literal null}. + * @param extension must not be {@literal null}. + * @param resourceLoader must not be {@literal null}. + * @param environment must not be {@literal null}. + */ + public CustomRepositoryBuilder(BeanDefinitionRegistry registry, RepositoryConfigurationExtension extension, + RepositoryConfigurationSource configurationSource, ResourceLoader resourceLoader, Environment environment) { + + Assert.notNull(extension, "RepositoryConfigurationExtension must not be null!"); + Assert.notNull(resourceLoader, "ResourceLoader must not be null!"); + Assert.notNull(environment, "Environment must not be null!"); + + this.registry = registry; + this.extension = extension; + this.resourceLoader = resourceLoader; + + this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); + + this.fragmentMetadata = new FragmentMetadata(metadataReaderFactory); + this.implementationDetector = new CustomRepositoryImplementationDetector(environment, resourceLoader, + configurationSource.toImplementationDetectionConfiguration(metadataReaderFactory)); + } + + /** + * Builds a new {@link BeanDefinitionBuilder} from the given {@link BeanDefinitionRegistry} and {@link ResourceLoader} + * . + * + * @param configuration must not be {@literal null}. + * @return + */ + public BeanDefinitionBuilder build(RepositoryConfiguration configuration) { + + Assert.notNull(registry, "BeanDefinitionRegistry must not be null!"); + Assert.notNull(resourceLoader, "ResourceLoader must not be null!"); + + BeanDefinitionBuilder builder = BeanDefinitionBuilder + .rootBeanDefinition(configuration.getRepositoryFactoryBeanClassName()); + + builder.getRawBeanDefinition().setSource(configuration.getSource()); + builder.addConstructorArgValue(configuration.getRepositoryInterface()); + builder.addPropertyValue("queryLookupStrategyKey", configuration.getQueryLookupStrategyKey()); + builder.addPropertyValue("lazyInit", configuration.isLazyInit()); + builder.setLazyInit(configuration.isLazyInit()); + + configuration.getRepositoryBaseClassName()// + .ifPresent(it -> builder.addPropertyValue("repositoryBaseClass", it)); + + NamedQueriesBeanDefinitionBuilder definitionBuilder = new NamedQueriesBeanDefinitionBuilder( + extension.getDefaultNamedQueryLocation()); + configuration.getNamedQueriesLocation().ifPresent(definitionBuilder::setLocations); + + builder.addPropertyValue("namedQueries", definitionBuilder.build(configuration.getSource())); + + registerCustomImplementation(configuration).ifPresent(it -> { + builder.addPropertyReference("customImplementation", it); + builder.addDependsOn(it); + }); + + BeanDefinitionBuilder fragmentsBuilder = BeanDefinitionBuilder + .rootBeanDefinition(RepositoryFragmentsFactoryBean.class); +// +// List fragmentBeanNames = registerRepositoryFragmentsImplementation(configuration) // +// .map(RepositoryFragmentConfiguration::getFragmentBeanName) // +// .collect(Collectors.toList()); + + fragmentsBuilder.addConstructorArgValue(Collections.emptyList()); + + builder.addPropertyValue("repositoryFragments", + ParsingUtils.getSourceBeanDefinition(fragmentsBuilder, configuration.getSource())); + + return builder; + } + + private Optional registerCustomImplementation(RepositoryConfiguration configuration) { + + ImplementationLookupConfiguration lookup = configuration.toLookupConfiguration(metadataReaderFactory); + + String beanName = lookup.getImplementationBeanName(); + + // Already a bean configured? + if (registry.containsBeanDefinition(beanName)) { + return Optional.of(beanName); + } + + Optional beanDefinition = implementationDetector.detectCustomImplementation(lookup); + + return beanDefinition.map(it -> { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Registering custom repository implementation: " + lookup.getImplementationBeanName() + " " + + it.getBeanClassName()); + } + + it.setSource(configuration.getSource()); + registry.registerBeanDefinition(beanName, it); + + return beanName; + }); + } + + private Stream registerRepositoryFragmentsImplementation( + RepositoryConfiguration configuration) { + + ImplementationDetectionConfiguration config = configuration + .toImplementationDetectionConfiguration(metadataReaderFactory); + + return fragmentMetadata.getFragmentInterfaces(configuration.getRepositoryInterface()) // + .map(it -> detectRepositoryFragmentConfiguration(it, config)) // + .flatMap(Optionals::toStream) // + .peek(it -> potentiallyRegisterFragmentImplementation(configuration, it)) // + .peek(it -> potentiallyRegisterRepositoryFragment(configuration, it)); + } + + private Optional detectRepositoryFragmentConfiguration(String fragmentInterface, + ImplementationDetectionConfiguration config) { + + ImplementationLookupConfiguration lookup = config.forFragment(fragmentInterface); + Optional beanDefinition = implementationDetector.detectCustomImplementation(lookup); + + return beanDefinition.map(bd -> new RepositoryFragmentConfiguration(fragmentInterface, bd)); + } + + private void potentiallyRegisterFragmentImplementation(RepositoryConfiguration repositoryConfiguration, + RepositoryFragmentConfiguration fragmentConfiguration) { + + String beanName = fragmentConfiguration.getImplementationBeanName(); + + // Already a bean configured? + if (registry.containsBeanDefinition(beanName)) { + return; + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(String.format("Registering repository fragment implementation: %s %s", beanName, + fragmentConfiguration.getClassName())); + } + + fragmentConfiguration.getBeanDefinition().ifPresent(bd -> { + + bd.setSource(repositoryConfiguration.getSource()); + registry.registerBeanDefinition(beanName, bd); + }); + } + + private void potentiallyRegisterRepositoryFragment(RepositoryConfiguration configuration, + RepositoryFragmentConfiguration fragmentConfiguration) { + + String beanName = fragmentConfiguration.getFragmentBeanName(); + + // Already a bean configured? + if (registry.containsBeanDefinition(beanName)) { + return; + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Registering repository fragment: " + beanName); + } + + BeanDefinitionBuilder fragmentBuilder = BeanDefinitionBuilder.rootBeanDefinition(RepositoryFragment.class, + "implemented"); + + fragmentBuilder.addConstructorArgValue(fragmentConfiguration.getInterfaceName()); + fragmentBuilder.addConstructorArgReference(fragmentConfiguration.getImplementationBeanName()); + + registry.registerBeanDefinition(beanName, + ParsingUtils.getSourceBeanDefinition(fragmentBuilder, configuration.getSource())); + } + + public BeanDefinitionRegistry getRegistry() { + return registry; + } +} 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 new file mode 100644 index 0000000..b532e6f --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/beans/repository/CustomRepositoryRegistrar.java @@ -0,0 +1,63 @@ +package com.flyfish.framework.beans.repository; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; +import org.springframework.data.mongodb.repository.config.MongoRepositoryConfigurationExtension; +import org.springframework.data.repository.config.*; + +/** + * 自定义的仓库注册器 + * @author wangyu + */ +public class CustomRepositoryRegistrar { + + // repo生成的extension + private final RepositoryConfigurationExtension extension = new MongoRepositoryConfigurationExtension(); + private final RepositoryConfigurationSource configurationSource; + private final CustomRepositoryBuilder builder; + + public CustomRepositoryRegistrar(AnnotationMetadata metadata, ResourceLoader resourceLoader, Environment environment, + BeanDefinitionRegistry registry, BeanNameGenerator generator) { + // repo生成的构建器 + this.configurationSource = new AnnotationRepositoryConfigurationSource(metadata, + EnableMongoRepositories.class, resourceLoader, environment, registry, generator); + // 构建器 + this.builder = new CustomRepositoryBuilder(registry, extension, + configurationSource, resourceLoader, environment); + } + + /** + * 根据class直接注册bean + * @param clazz 编译后的class + */ + public void register(Class clazz) { + //生成BeanDefinition并注册到容器中 + BeanDefinition origin = BeanDefinitionBuilder.genericBeanDefinition(clazz).getRawBeanDefinition(); + //设置当前bean定义对象是单例的 + origin.setScope("singleton"); + // 构造配置 + RepositoryConfiguration configuration = + new DefaultRepositoryConfiguration<>(configurationSource, origin, extension); + BeanDefinitionBuilder definitionBuilder = builder.build(configuration); + extension.postProcess(definitionBuilder, configurationSource); + extension.postProcess(definitionBuilder, (AnnotationRepositoryConfigurationSource) configurationSource); + + AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition(); + beanDefinition.setPrimary(configuration.isPrimary()); + String beanName = StringUtils.uncapitalize(clazz.getSimpleName()); + beanDefinition.setAttribute("factoryBeanObjectType", configuration.getRepositoryInterface()); + builder.getRegistry().registerBeanDefinition(beanName, beanDefinition); + } + + public RepositoryConfigurationExtension getExtension() { + return extension; + } +} 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 be397aa..54bfcd5 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,14 +1,10 @@ package com.flyfish.framework.beans.resolver; -import org.springframework.stereotype.Component; - /** * 动态Bean生成解析器 * * @author wangyu */ -@Component public class DynamicRestBeanResolver { - } 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 new file mode 100644 index 0000000..853a124 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/config/RestBeanAutoConfigure.java @@ -0,0 +1,181 @@ +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.compiler.DynamicJavaCompiler; +import com.flyfish.framework.compiler.support.JavaSource; +import com.flyfish.framework.compiler.template.TemplateCompiler; +import com.flyfish.framework.utils.Assert; +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; +import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.context.EnvironmentAware; +import org.springframework.context.ResourceLoaderAware; +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; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * rest bean的自动配置 + * + * @author wangyu + */ +@Slf4j +public class RestBeanAutoConfigure implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware, InitializingBean { + + // 预编译模板 + 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); + superClasses = new HashMap<>(); + superClasses.put("Controller", RestBean::controllerClass); + superClasses.put("Repository", RestBean::repoClass); + superClasses.put("Service", RestBean::serviceClass); + } + + /** + * 注册bean的支持,能够处理注解信息,注册bean等 + * + * @param metadata 导入类的元数据 + * @param registry 注册器 + */ + @Override + public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry, BeanNameGenerator beanNameGenerator) { + log.info("开始注册rest bean..."); + // 获取元数据 + Set basePackages = getBasePackages(metadata); + // 扫描包路径,检测RestBean注解的bean + Reflections reflections = new Reflections(basePackages); + // 编译器 + DynamicJavaCompiler compiler = DynamicJavaCompiler.delegate(); + // repo注册器 + CustomRepositoryRegistrar repositoryRegistrar = new CustomRepositoryRegistrar(metadata, resourceLoader, + environment, registry, beanNameGenerator); + // 准备注册的repo + Map>> loadedClasses = new ConcurrentHashMap<>(); + // 获取被注解的类,开始编译 + reflections.getTypesAnnotatedWith(RestBean.class).parallelStream().forEach(clazz -> { + RestBean restBean = clazz.getDeclaredAnnotation(RestBean.class); + if (null != restBean) { + // 创建java source信息 + JavaSource source = JavaSource.create() + .setBeanClass(clazz.getCanonicalName()) + .setQueryBeanClass(restBean.queryClass().getCanonicalName()) + .setPackageName(basePackages.stream().findFirst().orElse("com.flyfish.project")) + .setUri(restBean.value()); + // 分别生成实现类,从repo到controller + templates.forEach((key, template) -> { + source.setSuperClass(superClasses.get(key).apply(restBean).getCanonicalName()); + source.setClassName(clazz.getSimpleName() + key); + try { + log.info("尝试注册{}", source.getClassName()); + Class compiled = compiler.compile(source.getClassName() + ".java", template.apply(source)); + loadedClasses.computeIfAbsent(key, k -> new ArrayList<>()).add(compiled); + // IllegalAccessException | InvocationTargetException + } catch (ClassNotFoundException | IOException e) { + log.error("注册{}时发生异常!", source.getClassName(), e); + } + }); + } + }); + // 从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)); + log.info("结束注册rest bean..."); + } + + /** + * 注册bean,如果是controller,自动注册controller + * + * @param clazz class,要注册的class + * @param registry 注册器,注册bean用 + */ + private void registerBean(Class clazz, BeanDefinitionRegistry registry) { + //生成BeanDefinition并注册到容器中 + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz); + BeanDefinition beanDefinition = builder.getRawBeanDefinition(); + //设置当前bean定义对象是单利的 + beanDefinition.setScope("singleton"); + //将变量首字母置小写 + String beanName = StringUtils.uncapitalize(clazz.getSimpleName()); + log.info("注册bean:{}", beanName); + registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); + // 如果是controller,注册 + if (clazz.getSimpleName().endsWith("Controller")) { + controllers.add(beanName); + } + } + + /** + * 获取基本扫描包路径 + * + * @param metadata 元数据 + * @return 结果 + */ + private Set getBasePackages(AnnotationMetadata metadata) { + // 获取启动类的注解配置 + Map attrs = metadata.getAnnotationAttributes(EnableRestBeanDetect.class.getCanonicalName(), true); + Assert.isTrue(null != attrs && attrs.containsKey("basePackages"), "【rest配置】未指定包路径!"); + String[] packages = (String[]) attrs.get("basePackages"); + if (ArrayUtils.isEmpty(packages)) { + return Stream.of(metadata.getClassName()).collect(Collectors.toSet()); + } else { + return Arrays.stream(packages).filter(StringUtils::isNotBlank).collect(Collectors.toSet()); + } + } + + @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; + } + + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/controller/EnumController.java b/flyfish-web/src/main/java/com/flyfish/framework/controller/EnumController.java new file mode 100644 index 0000000..dd913b1 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/controller/EnumController.java @@ -0,0 +1,70 @@ +package com.flyfish.framework.controller; + +import com.flyfish.framework.bean.EnumValue; +import com.flyfish.framework.bean.Result; +import com.flyfish.framework.enums.NamedEnum; +import com.flyfish.framework.utils.StringFormats; +import org.apache.commons.lang3.ClassUtils; +import org.reflections.Reflections; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 统一处理枚举 + * 将来会加入字典自动处理 + * + * @author wangyu + */ +@RestController +@RequestMapping("enums/{code}") +public class EnumController { + + // 映射管理,避免class.forName扫描包路径 + private final Map> mapper = new HashMap<>(); + + private final Map> values = new HashMap<>(); + + public EnumController() { + Reflections reflections = new Reflections("com.flyfish.project"); + // 得到Resource注解的类 + Set> classSet = reflections.getSubTypesOf(NamedEnum.class); + // 注入 + classSet.stream().filter(clazz -> ClassUtils.isAssignable(clazz, Enum.class)).forEach(clazz -> { + String name = StringFormats.camel2Line(clazz.getSimpleName()); + List values = Arrays.stream(clazz.getEnumConstants()).reduce(new ArrayList<>(), (result, item) -> { + result.add(new EnumValue(((Enum) item).name(), item.getName())); + return result; + }, (a, b) -> a); + this.mapper.put(name, values.stream().collect(Collectors.toMap(EnumValue::getValue, EnumValue::getText))); + this.values.put(name, values); + }); + } + + /** + * 通过code和key查找显示名称 + * + * @param code 编码 + * @param key 键 + * @return 结果 + */ + @GetMapping("{key}") + public Result getText(@PathVariable("code") String code, @PathVariable("key") String key) { + return Result.ok(mapper.getOrDefault(code, Collections.emptyMap()).get(key)); + } + + /** + * 值的集合 + * + * @param code 编码 + * @return 结果 + */ + @GetMapping + public Result> values(@PathVariable("code") String code) { + return Result.ok(values.getOrDefault(code, Collections.emptyList())); + } +} \ No newline at end of file diff --git a/flyfish-web/src/main/java/com/flyfish/framework/controller/SafeController.java b/flyfish-web/src/main/java/com/flyfish/framework/controller/SafeController.java index ecd0f1d..255f5a8 100644 --- a/flyfish-web/src/main/java/com/flyfish/framework/controller/SafeController.java +++ b/flyfish-web/src/main/java/com/flyfish/framework/controller/SafeController.java @@ -1,4 +1,8 @@ package com.flyfish.framework.controller; +/** + * 安全的controller + * @author wangyu 标记代码归属 + */ public interface SafeController { } diff --git a/flyfish-web/src/main/resources/templates/Controller.tpl b/flyfish-web/src/main/resources/templates/Controller.tpl new file mode 100644 index 0000000..ac0dd65 --- /dev/null +++ b/flyfish-web/src/main/resources/templates/Controller.tpl @@ -0,0 +1,13 @@ +package #{packageName}; + +import #{superClass}; +import #{beanClass}; +import #{queryBeanClass}; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("#{uri}") +public class #{className} extends #{superClassName}<#{beanClassName}, #{queryBeanClassName}> { + +} \ No newline at end of file diff --git a/flyfish-web/src/main/resources/templates/Repository.tpl b/flyfish-web/src/main/resources/templates/Repository.tpl new file mode 100644 index 0000000..7b18dd2 --- /dev/null +++ b/flyfish-web/src/main/resources/templates/Repository.tpl @@ -0,0 +1,7 @@ +package #{packageName}; + +import #{superClass}; +import #{beanClass}; + +public interface #{className} extends #{superClassName}<#{beanClassName}> { +} diff --git a/flyfish-web/src/main/resources/templates/Service.tpl b/flyfish-web/src/main/resources/templates/Service.tpl new file mode 100644 index 0000000..fad4d3c --- /dev/null +++ b/flyfish-web/src/main/resources/templates/Service.tpl @@ -0,0 +1,9 @@ +package #{packageName}; + +import #{superClass}; +import #{beanClass}; +import org.springframework.stereotype.Service; + +@Service +public class #{className} extends #{superClassName}<#{beanClassName}> { +} diff --git a/pom.xml b/pom.xml index 77bf7c9..040d867 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ 1.8 Finchley.SR1 0.11.0 + 0.9.12 @@ -83,6 +84,16 @@ commons-io 2.6 + + com.itranswarp + compiler + 1.0 + + + org.reflections + reflections + ${reflection.version} + @@ -98,6 +109,10 @@ ${project.build.sourceEncoding} + + org.apache.maven.plugins + maven-source-plugin +