perf[orm]: 循环对象校验和格式语法分析校验

This commit is contained in:
jaysunxiao
2021-07-30 22:39:08 +08:00
parent 4eeba6d6d4
commit 1647b99ccf
7 changed files with 259 additions and 116 deletions
+2 -1
View File
@@ -6,12 +6,13 @@
- POJO对象的属性必须提供get和set方法,否则无法映射
- 不支持泛型
- 不支持循环引用的对象
- 如果不想映射某属性,直接加上transient关键字
- 目前支持基本数据属性(byteshortintlongfloatdoubleboolean),字符串StringListSet集合属性的映射,不支持Map
- 数据库主键能用整数尽量用整数,因为MongoDB默认的主键是一个字符串,比较占空间
- 数据库使用自研的orm框架,比如一个实体类UserEntity,映射到数据库中的集合为user,首字母小写,去掉Entity
- 基于 [caffeine](https://github.com/ben-manes/caffeine) 的高性能数据缓存
- 语法校验,如对没有加上get和set的字段自动语法提示
- 智能语法分析,错误的entity对象定义将无法启动程序并给出错误警告,
### Ⅲ. 使用方法
@@ -20,18 +20,22 @@ import com.mongodb.client.*;
import com.mongodb.client.model.IndexOptions;
import com.mongodb.client.model.Indexes;
import com.zfoo.orm.OrmContext;
import com.zfoo.orm.model.anno.EntityCache;
import com.zfoo.orm.model.anno.EntityCachesInjection;
import com.zfoo.orm.model.anno.*;
import com.zfoo.orm.model.cache.EntityCaches;
import com.zfoo.orm.model.cache.IEntityCaches;
import com.zfoo.orm.model.config.OrmConfig;
import com.zfoo.orm.model.entity.IEntity;
import com.zfoo.orm.model.vo.EntityDef;
import com.zfoo.orm.model.vo.IndexDef;
import com.zfoo.orm.model.vo.IndexTextDef;
import com.zfoo.protocol.collection.ArrayUtils;
import com.zfoo.protocol.collection.CollectionUtils;
import com.zfoo.protocol.exception.RunException;
import com.zfoo.protocol.util.AssertionUtils;
import com.zfoo.protocol.util.JsonUtils;
import com.zfoo.protocol.util.ReflectionUtils;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.util.math.RandomUtils;
import com.zfoo.util.net.HostAndPort;
import org.bson.Document;
import org.bson.codecs.configuration.CodecRegistries;
@@ -45,6 +49,7 @@ import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
@@ -77,7 +82,7 @@ public class OrmManager implements IOrmManager {
@Override
public void initBefore() {
var entityDefMap = scanEntity();
var entityDefMap = scanEntityClass();
for (var entityDef : entityDefMap.values()) {
var entityCaches = new EntityCaches(entityDef);
@@ -238,28 +243,25 @@ public class OrmManager implements IOrmManager {
return mongodbDatabase.getCollection(collection);
}
private Map<Class<? extends IEntity<?>>, EntityDef> scanEntity() {
private Map<Class<? extends IEntity<?>>, EntityDef> scanEntityClass() {
var cacheDefMap = new HashMap<Class<? extends IEntity<?>>, EntityDef>();
var entityPackage = ormConfig.getEntityPackage();
var cacheStrategies = ormConfig.getCachesConfig().getCacheStrategies();
var persisterStrategies = ormConfig.getPersistersConfig().getPersisterStrategies();
var locationSet = getEntityLocation(entityPackage);
var locationSet = scanEntityCacheAnno(ormConfig.getEntityPackage());
for (var location : locationSet) {
Class<?> entityClazz;
Class<? extends IEntity<?>> entityClazz;
try {
entityClazz = Class.forName(location);
entityClazz = (Class<? extends IEntity<?>>) Class.forName(location);
} catch (ClassNotFoundException e) {
throw new RuntimeException(StringUtils.format("无法获取实体类[{}]", location));
throw new RunException("无法获取实体类[{}]", location);
}
var cacheDef = EntityDef.valueOf(entityClazz, cacheStrategies, persisterStrategies);
var previousCacheDef = cacheDefMap.putIfAbsent((Class<? extends IEntity<?>>) entityClazz, cacheDef);
var cacheDef = parserEntityDef(entityClazz);
var previousCacheDef = cacheDefMap.putIfAbsent(entityClazz, cacheDef);
AssertionUtils.isNull(previousCacheDef, "缓存实体不能包含重复的[class:{}]", entityClazz.getSimpleName());
}
return cacheDefMap;
}
private Set<String> getEntityLocation(String scanLocation) {
private Set<String> scanEntityCacheAnno(String scanLocation) {
var prefixPattern = "classpath*:";
var suffixPattern = "**/*.class";
@@ -286,4 +288,207 @@ public class OrmManager implements IOrmManager {
throw new RuntimeException("无法读取实体信息:" + e);
}
}
public EntityDef parserEntityDef(Class<? extends IEntity<?>> clazz) {
analyze(clazz);
var cacheStrategies = ormConfig.getCachesConfig().getCacheStrategies();
var persisterStrategies = ormConfig.getPersistersConfig().getPersisterStrategies();
var cache = clazz.getAnnotation(EntityCache.class);
var cacheStrategyOptional = cacheStrategies.stream().filter(it -> it.getStrategy().equals(cache.cacheStrategy())).findFirst();
AssertionUtils.isTrue(cacheStrategyOptional.isPresent(), "实体类Entity[{}]没有找到缓存策略[{}]", clazz.getSimpleName(), cache.cacheStrategy());
var cacheStrategy = cacheStrategyOptional.get();
var cacheSize = cacheStrategy.getSize();
var expireMillisecond = cacheStrategy.getExpireMillisecond();
var idField = ReflectionUtils.getFieldsByAnnoInPOJOClass(clazz, Id.class)[0];
ReflectionUtils.makeAccessible(idField);
var persister = cache.persister();
var persisterStrategyOptional = persisterStrategies.stream().filter(it -> it.getStrategy().equals(persister.value())).findFirst();
AssertionUtils.isTrue(persisterStrategyOptional.isPresent(), "实体类Entity[{}]没有找到持久化策略[{}]", clazz.getSimpleName(), persister);
var persisterStrategy = persisterStrategyOptional.get();
var indexDefMap = new HashMap<String, IndexDef>();
var fields = ReflectionUtils.getFieldsByAnnoInPOJOClass(clazz, Index.class);
for (var field : fields) {
var indexAnnotation = field.getAnnotation(Index.class);
IndexDef indexDef = new IndexDef(field, indexAnnotation.ascending(), indexAnnotation.unique());
indexDefMap.put(field.getName(), indexDef);
}
var indexTextDefMap = new HashMap<String, IndexTextDef>();
fields = ReflectionUtils.getFieldsByAnnoInPOJOClass(clazz, IndexText.class);
for (var field : fields) {
IndexTextDef indexTextDef = new IndexTextDef(field, field.getAnnotation(IndexText.class));
indexTextDefMap.put(field.getName(), indexTextDef);
}
return EntityDef.valueOf(idField, clazz, cacheSize, expireMillisecond, persisterStrategy, indexDefMap, indexTextDefMap);
}
private void analyze(Class<?> clazz) {
// 是否实现了IEntity接口
AssertionUtils.isTrue(IEntity.class.isAssignableFrom(clazz), "被[{}]注解标注的实体类[{}]没有实现接口[{}]", EntityCache.class.getName(), clazz.getCanonicalName(), IEntity.class.getCanonicalName());
// 实体类Entity必须被注解EntityCache标注
AssertionUtils.notNull(clazz.getAnnotation(EntityCache.class), "实体类Entity[{}]必须被注解[{}]标注", clazz.getCanonicalName(), EntityCache.class.getName());
// 校验entity格式
var entitySubClassMap = new HashMap<Class<?>, Set<Class<?>>>();
checkEntity(clazz, entitySubClassMap);
// 对象循环引用检测
for (var entry : entitySubClassMap.entrySet()) {
var subClass = entry.getKey();
var subClassSet = entry.getValue();
if (subClassSet.contains(subClass)) {
throw new RunException("ORM[class:{}]在第一层包含循环引用对象[class:{}]", clazz.getSimpleName(), subClass.getSimpleName());
}
var queue = new LinkedList<>(subClassSet);
var allSubClassSet = new HashSet<>(queue);
while (!queue.isEmpty()) {
var firstSubClass = queue.poll();
if (entitySubClassMap.containsKey(firstSubClass)) {
for (var elementClass : entitySubClassMap.get(firstSubClass)) {
if (subClass.equals(elementClass)) {
throw new RunException("ORM[class:{}]在下层对象[class:{}]包含循环引用对象[class:{}]", clazz.getSimpleName(), elementClass.getSimpleName(), elementClass.getSimpleName());
}
if (!allSubClassSet.contains(elementClass)) {
allSubClassSet.add(elementClass);
queue.offer(elementClass);
}
}
}
}
}
// 校验id字段和id()方法的格式
var idFields = ReflectionUtils.getFieldsByAnnoInPOJOClass(clazz, Id.class);
AssertionUtils.isTrue(ArrayUtils.isNotEmpty(idFields) && idFields.length == 1, "实体类Entity[{}]必须只有且仅有一个Id注解", clazz.getSimpleName());
var idField = idFields[0];
// idField必须用private修饰
AssertionUtils.isTrue(Modifier.isPrivate(idField.getModifiers()), "实体类Entity[{}]的id必须是private私有的", clazz.getSimpleName());
if (clazz.isPrimitive() || Number.class.isAssignableFrom(clazz)) {
var entityInstance = ReflectionUtils.newInstance(clazz);
var idFieldType = idField.getType();
if (idFieldType.equals(int.class) || idFieldType.equals(Integer.class)) {
ReflectionUtils.setField(idField, entityInstance, RandomUtils.randomInt());
} else if (idFieldType.equals(long.class) || idFieldType.equals(Long.class)) {
ReflectionUtils.setField(idField, entityInstance, (long) RandomUtils.randomInt());
} else if (idFieldType.equals(float.class) || idFieldType.equals(Float.class)) {
ReflectionUtils.setField(idField, entityInstance, (float) RandomUtils.randomDouble());
} else if (idFieldType.equals(double.class) || idFieldType.equals(Double.class)) {
ReflectionUtils.setField(idField, entityInstance, (float) RandomUtils.randomDouble());
} else if (idFieldType.equals(String.class)) {
ReflectionUtils.setField(idField, entityInstance, RandomUtils.randomString(10));
}
}
}
private void checkEntity(Class<?> clazz, HashMap<Class<?>, Set<Class<?>>> entitySubClassMap) {
// 不需要检查重复的协议
if (entitySubClassMap.containsKey(clazz)) {
return;
}
entitySubClassMap.put(clazz, new HashSet<>());
// 是否为一个简单的javabean
ReflectionUtils.assertIsPojoClass(clazz);
// 不能是泛型类
AssertionUtils.isTrue(ArrayUtils.isEmpty(clazz.getTypeParameters()), "[class:{}]不能是泛型类", clazz.getCanonicalName());
// 必须要有一个空的构造器
ReflectionUtils.publicEmptyConstructor(clazz);
var filedList = Arrays.stream(clazz.getDeclaredFields())
.filter(it -> !Modifier.isTransient(it.getModifiers()))
.filter(it -> !Modifier.isStatic(it.getModifiers()))
.collect(Collectors.toList());
for (var field : filedList) {
// entity必须包含属性的get和set方法
ReflectionUtils.fieldToGetMethod(clazz, field);
ReflectionUtils.fieldToSetMethod(clazz, field);
// 是一个基本类型变量
var fieldType = field.getType();
if (isBaseType(fieldType)) {
// do nothing
} else if (fieldType.isArray()) {
// 是一个数组
Class<?> arrayClazz = fieldType.getComponentType();
checkSubEntity(clazz, arrayClazz, entitySubClassMap);
} else if (Set.class.isAssignableFrom(fieldType)) {
AssertionUtils.isTrue(fieldType.equals(Set.class), "ORM[class:{}]类型声明不正确,必须是Set接口类型", clazz.getCanonicalName());
Type type = field.getGenericType();
AssertionUtils.isTrue(type instanceof ParameterizedType, "ORM[class:{}]类型声明不正确,不是泛型类[field:{}]", clazz.getCanonicalName(), field.getName());
Type[] types = ((ParameterizedType) type).getActualTypeArguments();
AssertionUtils.isTrue(types.length == 1, "ORM[class:{}]中Set类型声明不正确,[field:{}]必须声明泛型类", clazz.getCanonicalName(), field.getName());
checkSubEntity(clazz, types[0], entitySubClassMap);
} else if (List.class.isAssignableFrom(fieldType)) {
// 是一个List
AssertionUtils.isTrue(fieldType.equals(List.class), "ORM[class:{}]类型声明不正确,必须是List接口类型", clazz.getCanonicalName());
Type type = field.getGenericType();
AssertionUtils.isTrue(type instanceof ParameterizedType, "ORM[class:{}]类型声明不正确,不是泛型类[field:{}]", clazz.getCanonicalName(), field.getName());
Type[] types = ((ParameterizedType) type).getActualTypeArguments();
AssertionUtils.isTrue(types.length == 1, "ORM[class:{}]中List类型声明不正确,[field:{}]必须声明泛型类", clazz.getCanonicalName(), field.getName());
checkSubEntity(clazz, types[0], entitySubClassMap);
} else if (Map.class.isAssignableFrom(fieldType)) {
throw new RunException("ORM[class:{}]类型声明不正确,不支持Map类型", clazz.getCanonicalName());
} else {
entitySubClassMap.get(clazz).add(fieldType);
checkEntity(fieldType, entitySubClassMap);
}
}
}
private void checkSubEntity(Class<?> currentEntityClass, Type type, HashMap<Class<?>, Set<Class<?>>> entitySubClassMap) {
if (type instanceof ParameterizedType) {
// 泛型类
Class<?> clazz = (Class<?>) ((ParameterizedType) type).getRawType();
if (Set.class.equals(clazz)) {
// Set<Set<String>>
checkSubEntity(currentEntityClass, ((ParameterizedType) type).getActualTypeArguments()[0], entitySubClassMap);
return;
} else if (List.class.equals(clazz)) {
// List<List<String>>
checkSubEntity(currentEntityClass, ((ParameterizedType) type).getActualTypeArguments()[0], entitySubClassMap);
return;
} else if (Map.class.equals(clazz)) {
// Map<List<String>, List<String>>
throw new RunException("ORM不支持Map类型");
}
} else if (type instanceof Class) {
Class<?> clazz = ((Class<?>) type);
if (isBaseType(clazz)) {
// do nothing
return;
} else if (clazz.getComponentType() != null) {
// 是一个二维以上数组
throw new RunException("ORM不支持多维数组或集合嵌套数组[type:{}]类型,仅支持一维数组", type);
} else if (clazz.equals(List.class) || clazz.equals(Set.class) || clazz.equals(Map.class)) {
throw new RunException("ORM不支持数组和集合联合使用[type:{}]类型", type);
} else {
entitySubClassMap.get(currentEntityClass).add(clazz);
checkEntity(clazz, entitySubClassMap);
return;
}
}
throw new RunException("[type:{}]类型不正确", type);
}
private boolean isBaseType(Class<?> clazz) {
return clazz.isPrimitive() || Number.class.isAssignableFrom(clazz) || String.class.isAssignableFrom(clazz);
}
}
@@ -13,23 +13,11 @@
package com.zfoo.orm.model.vo;
import com.zfoo.orm.model.anno.EntityCache;
import com.zfoo.orm.model.anno.Id;
import com.zfoo.orm.model.anno.Index;
import com.zfoo.orm.model.anno.IndexText;
import com.zfoo.orm.model.config.CacheStrategy;
import com.zfoo.orm.model.config.PersisterStrategy;
import com.zfoo.orm.model.entity.IEntity;
import com.zfoo.protocol.collection.ArrayUtils;
import com.zfoo.protocol.util.AssertionUtils;
import com.zfoo.protocol.util.ReflectionUtils;
import com.zfoo.protocol.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@@ -52,78 +40,19 @@ public class EntityDef {
private Map<String, IndexTextDef> indexTextDefMap;
private EntityDef() {
}
public static EntityDef valueOf(Class<?> clazz, List<CacheStrategy> cacheStrategies, List<PersisterStrategy> persisterStrategies) {
if (!IEntity.class.isAssignableFrom(clazz)) {
throw new IllegalArgumentException(StringUtils.format("被[{}]注解标注的实体类[{}]必须继承[{}]", EntityCache.class.getName(), clazz.getName(), IEntity.class.getName()));
}
public static EntityDef valueOf(Field idField, Class<? extends IEntity<?>> clazz, int cacheSize, long expireMillisecond
, PersisterStrategy persisterStrategy, Map<String, IndexDef> indexDefMap, Map<String, IndexTextDef> indexTextDefMap) {
var entityDef = new EntityDef();
entityDef.clazz = (Class<? extends IEntity<?>>) clazz;
try {
ReflectionUtils.makeAccessible(clazz.getDeclaredConstructor());
} catch (NoSuchMethodException e) {
throw new RuntimeException(StringUtils.format("实体类Entity[{}]必须包含一个默认的构造器", clazz.getSimpleName()));
}
var cache = clazz.getAnnotation(EntityCache.class);
AssertionUtils.notNull(cache);
var cacheStrategyOptional = cacheStrategies.stream().filter(it -> it.getStrategy().equals(cache.cacheStrategy())).findFirst();
if (cacheStrategyOptional.isEmpty()) {
throw new RuntimeException(StringUtils.format("实体类Entity[{}]没有找到缓存策略[{}]", clazz.getSimpleName(), cache.cacheStrategy()));
}
var cacheStrategy = cacheStrategyOptional.get();
entityDef.cacheSize = cacheStrategy.getSize();
entityDef.expireMillisecond = cacheStrategy.getExpireMillisecond();
var idFields = ReflectionUtils.getFieldsByAnnoInPOJOClass(clazz, Id.class);
AssertionUtils.isTrue(ArrayUtils.isNotEmpty(idFields) && idFields.length == 1, "实体类Entity[{}]必须只有且仅有一个Id注解", clazz.getSimpleName());
entityDef.idField = ReflectionUtils.getFieldsByAnnoInPOJOClass(clazz, Id.class)[0];
ReflectionUtils.makeAccessible(entityDef.idField);
// idField必须用private修饰
if (!Modifier.isPrivate(entityDef.idField.getModifiers())) {
throw new RuntimeException(StringUtils.format("实体类Entity[{}]的id必须是private私有的", clazz.getSimpleName()));
}
// entity必须包含属性的get和set方法
Arrays.stream(clazz.getDeclaredFields())
.filter(it -> !Modifier.isTransient(it.getModifiers()))
.forEach(it -> {
ReflectionUtils.fieldToGetMethod(clazz, it);
ReflectionUtils.fieldToSetMethod(clazz, it);
});
var persister = cache.persister();
AssertionUtils.notNull(persister);
var persisterStrategyOptional = persisterStrategies.stream().filter(it -> it.getStrategy().equals(persister.value())).findFirst();
if (persisterStrategyOptional.isEmpty()) {
throw new RuntimeException(StringUtils.format("实体类Entity[{}]没有找到持久化策略[{}]", clazz.getSimpleName(), persister));
}
entityDef.persisterStrategy = persisterStrategyOptional.get();
var indexDefMap = new HashMap<String, IndexDef>();
var fields = ReflectionUtils.getFieldsByAnnoInPOJOClass(clazz, Index.class);
for (Field field : fields) {
var indexAnnotation = field.getAnnotation(Index.class);
IndexDef indexDef = new IndexDef(field, indexAnnotation.ascending(), indexAnnotation.unique());
indexDefMap.put(field.getName(), indexDef);
}
var indexTextDefMap = new HashMap<String, IndexTextDef>();
fields = ReflectionUtils.getFieldsByAnnoInPOJOClass(clazz, IndexText.class);
for (Field field : fields) {
IndexTextDef indexTextDef = new IndexTextDef(field, field.getAnnotation(IndexText.class));
indexTextDefMap.put(field.getName(), indexTextDef);
}
entityDef.idField = idField;
entityDef.clazz = clazz;
entityDef.cacheSize = cacheSize;
entityDef.expireMillisecond = expireMillisecond;
entityDef.persisterStrategy = persisterStrategy;
entityDef.indexDefMap = indexDefMap;
entityDef.indexTextDefMap = indexTextDefMap;
return entityDef;
}
public Class<? extends IEntity<?>> getClazz() {
return clazz;
}
@@ -28,7 +28,7 @@ import com.zfoo.orm.model.entity.IEntity;
public class MailEnt implements IEntity<String> {
@Id
private String mailId;
private String id;
@Index(ascending = true, unique = false)
private String userName;
@@ -38,23 +38,23 @@ public class MailEnt implements IEntity<String> {
public MailEnt() {
}
public MailEnt(String mailId, String userName, String content) {
this.mailId = mailId;
public MailEnt(String id, String userName, String content) {
this.id = id;
this.userName = userName;
this.content = content;
}
@Override
public String id() {
return mailId;
return id;
}
public String getMailId() {
return mailId;
public String getId() {
return id;
}
public void setMailId(String mailId) {
this.mailId = mailId;
public void setId(String id) {
this.id = id;
}
public String getUserName() {
@@ -73,12 +73,4 @@ public class MailEnt implements IEntity<String> {
this.content = content;
}
@Override
public String toString() {
return "MailEnt{" +
"mailId='" + mailId + '\'' +
", playName='" + userName + '\'' +
", content='" + content + '\'' +
'}';
}
}
+2
View File
@@ -73,6 +73,8 @@ cpu i9900k
如果考虑支持修改协议类属性名称,必须让字段的读写顺序可控,这就需要注解来标识属性的顺序(protostuff就是这样做的),但是感觉这样不优雅。
如果考虑支持字段增加和减少,需要消耗5%左右的性能(预估),并且增加一倍的包体积大小(写入字段的顺序),感觉不是非常划算。
因为可以通过协议版本号来解决这个问题,所以去支持这样的增删操作动力并不是非常的大。
对于服务器来说协议一般有对内和对外的协议,对外protobuf用起来还行,但是服务器的内部调用protobuf用的就少了,用zfoo就可以统一对内和对外的协议。
```
### Ⅵ. 协议规范
@@ -255,12 +255,8 @@ public class ProtocolAnalysis {
AssertionUtils.isTrue(clazz.getSimpleName().matches("[a-zA-Z0-9_]*"), "[class:{}]的命名只能包含字母,数字,下划线", clazz.getCanonicalName(), PROTOCOL_ID);
// 必须要有一个空的构造器
Constructor<?> constructor;
try {
constructor = clazz.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
throw new UnknownException(e, "[class:{}]协议序列号[{}]必须有一个空的构造器", clazz.getCanonicalName(), PROTOCOL_ID);
}
Constructor<?> constructor = ReflectionUtils.publicEmptyConstructor(clazz);
ReflectionUtils.makeAccessible(protocolIdField);
IPacket packet = (IPacket) constructor.newInstance();
@@ -468,7 +464,7 @@ public class ProtocolAnalysis {
} else if (List.class.equals(clazz)) {
// List<List<String>>
IFieldRegistration registration = typeToRegistration(currentProtocolClass, ((ParameterizedType) type).getActualTypeArguments()[0]);
return ListField.valueOf(registration, (ParameterizedType) type);
return ListField.valueOf(registration, type);
} else if (Map.class.equals(clazz)) {
// Map<List<String>, List<String>>
IFieldRegistration keyRegistration = typeToRegistration(currentProtocolClass, ((ParameterizedType) type).getActualTypeArguments()[0]);
@@ -14,6 +14,7 @@ package com.zfoo.protocol.util;
import com.zfoo.protocol.collection.ArrayUtils;
import com.zfoo.protocol.exception.RunException;
import com.zfoo.protocol.exception.UnknownException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
@@ -26,6 +27,7 @@ import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* 反射工具类
*
@@ -81,6 +83,22 @@ public abstract class ReflectionUtils {
}
}
public static Constructor<?> publicEmptyConstructor(Class<?> clazz) {
Constructor<?> constructor;
try {
constructor = clazz.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
throw new UnknownException(e, "[class:{}] should have exactly one public zero-argument constructor", clazz.getCanonicalName());
}
if (!Modifier.isPublic(constructor.getModifiers())) {
throw new UnknownException("[class:{}] should have exactly one public zero-argument constructor", clazz.getCanonicalName());
}
return constructor;
}
/**
* 标准的属性名称更加通用,前缀不能是is,否则属性名称在不同语言很难去统一get和set方法
*/