增加ToString类

This commit is contained in:
zhouhao
2018-06-07 18:01:57 +08:00
parent b053cab219
commit f5cfe11d64
12 changed files with 547 additions and 18 deletions

View File

@@ -17,6 +17,9 @@
*/
package org.hswebframework.web.commons.entity;
import org.hswebframework.web.bean.ToString;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -35,6 +38,10 @@ public interface GenericEntity<PK> extends CloneableEntity {
void setId(PK id);
default String toString(String... ignoreProperty) {
return ToString.toString(this, ignoreProperty);
}
default Map<String, Object> getProperties() {
return null;
}

View File

@@ -35,6 +35,11 @@ public abstract class SimpleGenericEntity<PK> implements GenericEntity<PK> {
private Map<String, Object> properties;
@Override
public String toString() {
return toString((String[]) null);
}
@Override
public PK getId() {
return this.id;

View File

@@ -10,5 +10,6 @@ public interface Copier {
default void copy(Object source, Object target, String... ignore){
copy(source,target,new HashSet<>(Arrays.asList(ignore)),FastBeanCopier.DEFAULT_CONVERT);
}
}

View File

@@ -0,0 +1,342 @@
package org.hswebframework.web.bean;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.hswebframework.utils.time.DateFormatter;
import org.springframework.beans.BeanUtils;
import org.springframework.core.annotation.AnnotationUtils;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static org.hswebframework.web.bean.ToString.Feature.coverIgnoreProperty;
import static org.hswebframework.web.bean.ToString.Feature.disableNestProperty;
import static org.hswebframework.web.bean.ToString.Feature.nullPropertyToEmpty;
/**
* @author zhouhao
* @since 3.0.0-RC
*/
@Slf4j
public class DefaultToStringOperator<T> implements ToStringOperator<T> {
private PropertyDescriptor[] descriptors;
private Set<String> defaultIgnoreProperties;
private long defaultFeatures = ToString.DEFAULT_FEATURE;
private Map<String, PropertyDescriptor> descriptorMap;
private Map<String, BiFunction<Object, ConvertConfig, Object>> converts;
private Function<Object, String> coverStringConvert = (o) -> coverString(String.valueOf(o), 50);
private Function<Class, BiFunction<Object, ConvertConfig, Object>> simpleConvertBuilder = type -> {
if (Date.class.isAssignableFrom(type)) {
return (value, f) -> DateFormatter.toString(((Date) value), "yyyy-MM-dd HH:mm:ss");
} else {
return (value, f) -> value;
}
};
Predicate<Class> simpleTypePredicate = ((Predicate<Class>) String.class::isAssignableFrom)
.or(Class::isEnum)
.or(Class::isPrimitive)
.or(Date.class::isAssignableFrom)
.or(Number.class::isAssignableFrom)
.or(Boolean.class::isAssignableFrom);
private Class<T> targetType;
public DefaultToStringOperator(Class<T> targetType) {
this.targetType = targetType;
descriptors = BeanUtils.getPropertyDescriptors(targetType);
init();
}
public static String coverString(String str, double percent) {
if (str.length() == 1) {
return "*";
}
if (percent > 1) {
percent = percent / 100d;
}
percent = 1 - percent;
long size = Math.round(str.length() * percent);
long end = (str.length() - size / 2);
long start = str.length() - end;
start = start == 0 && percent > 0 ? 1 : start;
char[] chars = str.toCharArray();
for (int i = 0; i < chars.length; i++) {
if (i >= start && i <= end - 1) {
chars[i] = '*';
}
}
return new String(chars);
}
@SuppressWarnings("all")
protected void init() {
converts = new HashMap<>();
descriptorMap = Arrays.stream(descriptors).collect(Collectors.toMap(PropertyDescriptor::getName, Function.identity()));
//获取类上的注解
ToString.Ignore classIgnore = AnnotationUtils.getAnnotation(targetType, ToString.Ignore.class);
ToString.Features features = AnnotationUtils.getAnnotation(targetType, ToString.Features.class);
if (null != features && features.value().length > 0) {
defaultFeatures = ToString.Feature.createFeatures(features.value());
} else {
defaultFeatures = ToString.DEFAULT_FEATURE;
}
defaultIgnoreProperties = classIgnore == null ?
new HashSet<>(Collections.emptySet())
: new HashSet<>(Arrays.asList(classIgnore.value()));
//是否打码
boolean defaultCover = classIgnore != null && classIgnore.cover();
for (PropertyDescriptor descriptor : descriptors) {
if ("class".equals(descriptor.getName())) {
continue;
}
Class propertyType = descriptor.getPropertyType();
String propertyName = descriptor.getName();
BiFunction<Object, ConvertConfig, Object> convert;
ToString.Ignore propertyIgnore = null;
long propertyFeature = 0;
try {
Field field = targetType.getDeclaredField(descriptor.getName());
propertyIgnore = field.getAnnotation(ToString.Ignore.class);
features = AnnotationUtils.getAnnotation(field, ToString.Features.class);
if (propertyIgnore != null) {
for (String val : propertyIgnore.value()) {
defaultIgnoreProperties.add(field.getName().concat(".").concat(val));
}
}
if (null != features && features.value().length > 0) {
propertyFeature = ToString.Feature.createFeatures(features.value());
}
} catch (NoSuchFieldException e) {
log.warn("无法获取字段{},该字段将不会被打码!", descriptor.getName());
}
//是否设置了打码
boolean cover = (propertyIgnore == null && defaultCover) || (propertyIgnore != null && propertyIgnore.cover());
//是否注解了ignore
boolean hide = propertyIgnore != null;
long finalPropertyFeature = propertyFeature;
if (simpleTypePredicate.test(propertyType)) {
BiFunction<Object, ConvertConfig, Object> simpleConvert = simpleConvertBuilder.apply(propertyType);
convert = (value, f) -> {
long feature = finalPropertyFeature == 0 ? f.features : finalPropertyFeature;
value = simpleConvert.apply(value, f);
if (hide || f.ignoreProperty.contains(propertyName)) {
if (ToString.Feature.hasFeature(feature, ToString.Feature.coverIgnoreProperty)) {
return coverStringConvert.apply(value);
} else {
return null;
}
}
return value;
};
} else {
boolean toStringOverride = false;
try {
toStringOverride = propertyType.getMethod("toString").getDeclaringClass() != Object.class;
} catch (NoSuchMethodException ignore) {
}
boolean finalToStringOverride = toStringOverride;
boolean justReturn = propertyType.isArray()
|| Collection.class.isAssignableFrom(propertyType)
|| Map.class.isAssignableFrom(propertyType);
convert = (value, f) -> {
if (f.ignoreProperty.contains(propertyName)) {
return null;
}
long feature = finalPropertyFeature == 0 ? f.features : finalPropertyFeature;
boolean jsonFormat = ToString.Feature.hasFeature(feature, ToString.Feature.jsonFormat);
boolean propertyJsonFormat = ToString.Feature.hasFeature(finalPropertyFeature, ToString.Feature.jsonFormat);
if (ToString.Feature.hasFeature(f.features, disableNestProperty)) {
return null;
}
if (!jsonFormat && finalToStringOverride) {
return String.valueOf(value);
}
Set<String> newIgnoreProperty = f.ignoreProperty
.stream()
.filter(property -> property.startsWith(propertyName.concat(".")))
.map(property -> property.substring(propertyName.length() + 1))
.collect(Collectors.toSet());
if (justReturn) {
if (value instanceof Object[]) {
value = Arrays.asList(((Object[]) value));
}
if (value instanceof Map) {
value = convertMap(((Map) value), feature, newIgnoreProperty);
}
if (value instanceof Collection) {
value = ((Collection) value).stream()
.map((val) -> {
if (val instanceof Map) {
return convertMap(((Map) val), feature, newIgnoreProperty);
}
if (simpleTypePredicate.test(val.getClass())) {
return val;
}
ToStringOperator operator = ToString.getOperator(val.getClass());
if (operator instanceof DefaultToStringOperator) {
return ((DefaultToStringOperator) operator).toMap(val, feature, newIgnoreProperty);
}
return operator.toString(val, feature, newIgnoreProperty);
}).collect(Collectors.toList());
}
if (value instanceof Map) {
value = convertMap(((Map) value), feature, newIgnoreProperty);
}
if (propertyJsonFormat) {
return JSON.toJSONString(value);
}
return value;
}
ToStringOperator operator = ToString.getOperator(value.getClass());
if (!propertyJsonFormat && operator instanceof DefaultToStringOperator) {
return ((DefaultToStringOperator) operator).toMap(value, feature, newIgnoreProperty);
} else {
return operator.toString(value, feature, newIgnoreProperty);
}
};
}
converts.put(descriptor.getName(), convert);
}
}
class ConvertConfig {
long features;
Set<String> ignoreProperty;
}
protected Map<String, Object> convertMap(Map<String, Object> obj, long features, Set<String> ignoreProperty) {
if (ignoreProperty.isEmpty()) {
return obj;
}
boolean cover = ToString.Feature.hasFeature(features, coverIgnoreProperty);
boolean isNullPropertyToEmpty = ToString.Feature.hasFeature(features, nullPropertyToEmpty);
boolean isDisableNestProperty = ToString.Feature.hasFeature(features, disableNestProperty);
Map<String, Object> newMap = new HashMap<>(obj);
Set<String> ignore = new HashSet<>(ignoreProperty.size());
ignore.addAll(defaultIgnoreProperties);
for (Map.Entry<String, Object> entry : newMap.entrySet()) {
Object value = entry.getValue();
if (value == null) {
if (isNullPropertyToEmpty) {
entry.setValue("");
}
continue;
}
Class type = value.getClass();
if (simpleTypePredicate.test(type)) {
value = simpleConvertBuilder.apply(type).apply(value, null);
if (ignoreProperty.contains(entry.getKey())) {
if (cover) {
value = coverStringConvert.apply(value);
} else {
ignore.add(entry.getKey());
}
entry.setValue(value);
}
} else {
if (isDisableNestProperty) {
ignore.add(entry.getKey());
}
}
}
ignore.forEach(newMap::remove);
return newMap;
}
protected Map<String, Object> toMap(T target, long features, Set<String> ignoreProperty) {
Map<String, Object> map = target instanceof Map ? ((Map) target) : FastBeanCopier.copy(target, new LinkedHashMap<>());
Set<String> ignore = ignoreProperty == null || ignoreProperty.isEmpty() ? defaultIgnoreProperties : ignoreProperty;
ConvertConfig convertConfig = new ConvertConfig();
convertConfig.ignoreProperty = ignore;
convertConfig.features = features == -1 ? defaultFeatures : features;
Set<String> realIgnore = new HashSet<>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
Object value = entry.getValue();
if (value == null) {
if (ToString.Feature.hasFeature(features, ToString.Feature.nullPropertyToEmpty)) {
boolean isSimpleType = false;
PropertyDescriptor propertyDescriptor = descriptorMap.get(entry.getKey());
Class propertyType = null;
if (propertyDescriptor != null) {
propertyType = propertyDescriptor.getPropertyType();
isSimpleType = simpleTypePredicate.test(propertyType);
}
if (isSimpleType || propertyType == null) {
entry.setValue("");
} else if (propertyType.isArray() || Collection.class.isAssignableFrom(propertyType)) {
entry.setValue(Collections.emptyList());
} else {
entry.setValue(Collections.emptyMap());
}
}
continue;
}
BiFunction<Object, ConvertConfig, Object> converter = converts.get(entry.getKey());
if (null != converter) {
entry.setValue(converter.apply(value, convertConfig));
}
if (entry.getValue() == null) {
realIgnore.add(entry.getKey());
}
}
realIgnore.forEach(map::remove);
return map;
}
@Override
public String toString(T target, long features, Set<String> ignoreProperty) {
if (target == null) {
return "";
}
if (features == -1) {
features = defaultFeatures;
}
Map<String, Object> mapValue = toMap(target, features, ignoreProperty);
if (ToString.Feature.hasFeature(features, ToString.Feature.jsonFormat)) {
return JSON.toJSONString(mapValue);
}
StringJoiner joiner = new StringJoiner(", ", target.getClass().getSimpleName() + "(", ")");
mapValue.forEach((key, value) -> joiner.add(key.concat("=").concat(String.valueOf(value))));
return joiner.toString();
}
}

View File

@@ -0,0 +1,134 @@
package org.hswebframework.web.bean;
import org.springframework.util.ClassUtils;
import java.lang.annotation.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author zhouhao
* @since 3.0.0-RC
*/
public class ToString {
public static long DEFAULT_FEATURE = Feature.createFeatures(
Feature.coverIgnoreProperty
, Feature.nullPropertyToEmpty
// , Feature.jsonFormat
);
public static final Map<Class, ToStringOperator> cache = new ConcurrentHashMap<>();
@SuppressWarnings("all")
public static <T> ToStringOperator<T> getOperator(Class<T> type) {
return cache.computeIfAbsent(type, DefaultToStringOperator::new);
}
@SuppressWarnings("all")
public static <T> String toString(T target) {
return getOperator((Class<T>) ClassUtils.getUserClass(target)).toString(target);
}
@SuppressWarnings("all")
public static <T> String toString(T target, String... ignoreProperty) {
return getOperator((Class<T>) ClassUtils.getUserClass(target)).toString(target, ignoreProperty);
}
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Ignore {
String[] value() default {};
boolean cover() default true;
}
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Features {
Feature[] value() default {};
}
public enum Feature {
/**
* 什么也不配置
*
* @since 3.0.0-RC
*/
empty,
/**
* 忽略为null的字段
*
* @since 3.0.0-RC
*/
ignoreNullProperty,
/**
* null的字段转为空,如null字符串转为"", null的list转为[]
*
* @since 3.0.0-RC
*/
nullPropertyToEmpty,
/**
* 排除的字段使用*进行遮盖,如: 张三 =? 张* , 18502314087 => 185****087
*
* @since 3.0.0-RC
*/
coverIgnoreProperty,
/**
* 是否关闭嵌套属性toString
*
* @since 3.0.0-RC
*/
disableNestProperty,
/**
* 以json方式进行格式化
*
* @since 3.0.0-RC
*/
jsonFormat;
public long getMask() {
return 1L << ordinal();
}
public static boolean hasFeature(long features, Feature feature) {
long mast = feature.getMask();
return (features & mast) == mast;
}
public static long removeFeatures(long oldFeature, Feature... features) {
if (features == null) {
return 0L;
}
long value = oldFeature;
for (Feature feature : features) {
value &= ~feature.getMask();
}
return value;
}
public static long createFeatures(Feature... features) {
if (features == null) {
return 0L;
}
long value = 0L;
for (Feature feature : features) {
value |= feature.getMask();
}
return value;
}
}
}

View File

@@ -0,0 +1,17 @@
package org.hswebframework.web.bean;
import java.util.*;
/**
* @author zhouhao
* @since 3.0.0-RC
*/
public interface ToStringOperator<T> {
default String toString(T target, String... ignoreProperty) {
return toString(target, -1, ignoreProperty == null ? Collections.emptySet() : new HashSet<>(Arrays.asList(ignoreProperty)));
}
String toString(T target, long features, Set<String> ignoreProperty);
}

View File

@@ -26,6 +26,7 @@ public class FastBeanCopierTest {
source.setNestObject2(Collections.singletonMap("name","mapTest"));
NestObject nestObject = new NestObject();
nestObject.setAge(10);
nestObject.setPassword("1234567");
nestObject.setName("测试2");
source.setNestObject(nestObject);
source.setNestObject3(nestObject);
@@ -35,9 +36,9 @@ public class FastBeanCopierTest {
long t = System.currentTimeMillis();
for (int i = 10_0000; i > 0; i--) {
FastBeanCopier.copy(source, target);
}
// for (int i = 10_0000; i > 0; i--) {
// FastBeanCopier.copy(source, target);
// }
System.out.println(System.currentTimeMillis() - t);
System.out.println(source);

View File

@@ -1,15 +1,13 @@
package org.hswebframework.web.bean;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.*;
/**
* @author zhouhao
* @since
*/
@Data
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@@ -19,8 +17,16 @@ public class NestObject implements Cloneable {
private int age;
@ToString.Ignore
private String password ;
@Override
public NestObject clone() throws CloneNotSupportedException {
return (NestObject)super.clone();
return (NestObject) super.clone();
}
@Override
public String toString() {
return ToString.toString(this);
}
}

View File

@@ -31,11 +31,11 @@ public class Source {
private NestObject nestObject;
private List<NestObject> nestObjects = Arrays.asList(new NestObject("test", 1),new NestObject("test", 1));
private List<NestObject> nestObjects = Arrays.asList(new NestObject("test", 1, "1234567"), new NestObject("test", 1, "1234567"));
private Map<String, Object> nestObject2 = new HashMap<>();
private NestObject nestObject3;
private NestObject nestObject3 = new NestObject("test", 1, "1234567");
private Color color = Color.RED;

View File

@@ -1,6 +1,8 @@
package org.hswebframework.web.bean;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
@@ -8,15 +10,20 @@ import java.util.Date;
import java.util.List;
import java.util.Map;
@Data
import static org.hswebframework.web.bean.ToString.Feature.coverIgnoreProperty;
import static org.hswebframework.web.bean.ToString.Feature.jsonFormat;
import static org.hswebframework.web.bean.ToString.Feature.nullPropertyToEmpty;
@Getter
@Setter
public class Target {
private String name;
private String name;
private String[] ids;
private Boolean boy;
private boolean boy2;
private String boy3;
private String boy3;
private int age;
@@ -24,21 +31,24 @@ public class Target {
private String age3;
private Date deleteTime=new Date();
private Date deleteTime = new Date();
private String createTime;
private Date updateTime;
@ToString.Features({coverIgnoreProperty,jsonFormat})
@ToString.Ignore(value = "password")
private NestObject nestObject;
private NestObject nestObject2;
private List<Map<String,Object>> nestObjects;
@ToString.Ignore(value = "password")
private List<Map<String, Object>> nestObjects;
@ToString.Ignore("password")
private Map<String, Object> nestObject3;
private int color;
private Color color2;
@@ -55,4 +65,8 @@ public class Target {
private Integer[] arr4;
@Override
public String toString() {
return ToString.toString(this);
}
}

View File

@@ -115,7 +115,6 @@ public class HswebAutoConfiguration {
if (type instanceof Class) {
Class classType = ((Class) type);
if (classType.isEnum()) {
// TODO: 2018/4/12 支持EnumDict枚举的反序列化
return super.getDeserializer(type);
}
checkAutoType(type.getTypeName(), ((Class) type));

View File

@@ -3,6 +3,7 @@ package org.hswebframework.web.entity.authorization;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hswebframework.web.bean.ToString;
import org.hswebframework.web.commons.entity.SimpleGenericEntity;
/**
@@ -18,8 +19,10 @@ public class SimpleUserEntity extends SimpleGenericEntity<String> implements Use
private String username;
@ToString.Ignore
private String password;
@ToString.Ignore(cover = false)
private String salt;
private Long createTime;