Feat:增加动态代码编译引擎相关逻辑

This commit is contained in:
wangyu 2021-01-04 14:44:03 +08:00
parent 66a1f23216
commit 392b1ebc09
24 changed files with 1171 additions and 5 deletions

View File

@ -59,6 +59,14 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.itranswarp</groupId>
<artifactId>compiler</artifactId>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<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, 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 + "");
}
}

View File

@ -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, ".");
}
}

View File

@ -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<String> options = Arrays.asList("-d", "./bin");
// 编译池
private final Map<String, Class<?>> 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();
}
}

View File

@ -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;
}
}

View File

@ -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<JavaSource, String> compile(Class<?> dataClass, String filename) {
// 获取文件内容
InputStream fis = TemplateCompiler.class.getResourceAsStream(filename);
try {
// 获取模板内容
String template = StreamUtils.copyToString(fis, StandardCharsets.UTF_8);
// 准备变量池
Map<String, PropertyDescriptor> 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<String, String> 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<JavaSource, String> compile(String filename) {
return compile(JavaSource.class, filename);
}
}

View File

@ -0,0 +1,47 @@
package com.flyfish.framework.utils;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* map构造器
*
* @author wangyu
*/
public class MapBuilder<K, V> {
// 内部的map
private final Map<K, V> map = new LinkedHashMap<>();
/**
* 构造器
*
* @param <K>
* @param <V>
* @return 结果
*/
public static <K, V> MapBuilder<K, V> builder() {
return new MapBuilder<>();
}
/**
* 放置参数
*
* @param k
* @param v
* @return 结果
*/
public MapBuilder<K, V> put(K k, V v) {
this.map.put(k, v);
return this;
}
/**
* 构建
*
* @return 结果
*/
public Map<K, V> build() {
return map;
}
}

View File

@ -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();
}
}

View File

@ -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";
}

View File

@ -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<String, Map<String, String>> mapper = new HashMap<>();
private final Map<String, List<EnumValue>> values = new HashMap<>();
public EnumConfig() {
Reflections reflections = new Reflections("com.flyfish.project");
// 得到Resource注解的类
Set<Class<? extends NamedEnum>> classSet = reflections.getSubTypesOf(NamedEnum.class);
// 注入
classSet.stream().filter(clazz -> ClassUtils.isAssignable(clazz, Enum.class)).forEach(clazz -> {
String name = StringFormats.camel2Line(clazz.getSimpleName());
List<EnumValue> 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 {
}
}

View File

@ -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";
}

View File

@ -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<? extends Qo<?>> queryClass();
/**
* 仓库的超类默认是default
*
* @return 结果
*/
Class<? extends DefaultRepository> repoClass() default DefaultRepository.class;
/**
* 服务类动态生成服务
*
* @return 结果
*/
Class<? extends BaseService> serviceClass() default BaseServiceImpl.class;
/**
* controller的类支持自定义
*
* @return 结果
*/
Class<? extends SafeController> controllerClass() default BaseController.class;
}

View File

@ -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<String> 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<String> 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<AbstractBeanDefinition> 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<RepositoryFragmentConfiguration> 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<RepositoryFragmentConfiguration> detectRepositoryFragmentConfiguration(String fragmentInterface,
ImplementationDetectionConfiguration config) {
ImplementationLookupConfiguration lookup = config.forFragment(fragmentInterface);
Optional<AbstractBeanDefinition> 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;
}
}

View File

@ -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<? extends RepositoryConfigurationSource> 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;
}
}

View File

@ -1,14 +1,10 @@
package com.flyfish.framework.beans.resolver;
import org.springframework.stereotype.Component;
/**
* 动态Bean生成解析器
*
* @author wangyu
*/
@Component
public class DynamicRestBeanResolver {
}

View File

@ -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<String, Function<JavaSource, String>> templates = Stream.of("Repository", "Service", "Controller")
.collect(Collectors.toMap(key -> key, filename -> TemplateCompiler.compile("/templates/" + filename + ".tpl")));
private final Map<String, Function<RestBean, Class<?>>> superClasses;
private final Method handlerRegister;
private final List<String> 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<String> 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<String, List<Class<?>>> 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<String> getBasePackages(AnnotationMetadata metadata) {
// 获取启动类的注解配置
Map<String, Object> 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;
}
}

View File

@ -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<String, Map<String, String>> mapper = new HashMap<>();
private final Map<String, List<EnumValue>> values = new HashMap<>();
public EnumController() {
Reflections reflections = new Reflections("com.flyfish.project");
// 得到Resource注解的类
Set<Class<? extends NamedEnum>> classSet = reflections.getSubTypesOf(NamedEnum.class);
// 注入
classSet.stream().filter(clazz -> ClassUtils.isAssignable(clazz, Enum.class)).forEach(clazz -> {
String name = StringFormats.camel2Line(clazz.getSimpleName());
List<EnumValue> 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<String> 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<List<EnumValue>> values(@PathVariable("code") String code) {
return Result.ok(values.getOrDefault(code, Collections.emptyList()));
}
}

View File

@ -1,4 +1,8 @@
package com.flyfish.framework.controller;
/**
* 安全的controller
* @author wangyu 标记代码归属
*/
public interface SafeController {
}

View File

@ -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}> {
}

View File

@ -0,0 +1,7 @@
package #{packageName};
import #{superClass};
import #{beanClass};
public interface #{className} extends #{superClassName}<#{beanClassName}> {
}

View File

@ -0,0 +1,9 @@
package #{packageName};
import #{superClass};
import #{beanClass};
import org.springframework.stereotype.Service;
@Service
public class #{className} extends #{superClassName}<#{beanClassName}> {
}

15
pom.xml
View File

@ -24,6 +24,7 @@
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
<jjwt.version>0.11.0</jjwt.version>
<reflection.version>0.9.12</reflection.version>
</properties>
<developers>
@ -83,6 +84,16 @@
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>com.itranswarp</groupId>
<artifactId>compiler</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>${reflection.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
@ -98,6 +109,10 @@
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>