优化拓展实体支持

This commit is contained in:
zhou-hao
2021-06-21 18:21:00 +08:00
parent 208f9b34dd
commit c6effa9f0f
15 changed files with 218 additions and 51 deletions

View File

@@ -38,6 +38,13 @@
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -13,12 +13,14 @@
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*
*/
package org.hswebframework.web.api.crud.entity;
import javax.annotation.Nullable;
/**
* 实体工厂接口,系统各个地方使用此接口来创建实体,在实际编码中也应该使用此接口来创建实体,而不是使用new方式来创建
*
@@ -97,11 +99,12 @@ public interface EntityFactory {
* @param <T> 泛型
* @return 实体类型
*/
default <T> Class<T> getInstanceType(Class<T> entityClass){
return getInstanceType(entityClass,false);
}
default <T> Class<T> getInstanceType(Class<T> entityClass) {
return getInstanceType(entityClass, false);
}
<T> Class<T> getInstanceType(Class<T> entityClass,boolean autoRegister);
@Nullable
<T> Class<T> getInstanceType(Class<T> entityClass, boolean autoRegister);
/**
* 拷贝对象的属性

View File

@@ -0,0 +1,30 @@
package org.hswebframework.web.api.crud.entity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Component;
import java.util.function.Supplier;
@Component
@Slf4j
public final class EntityFactoryHolder {
static EntityFactory FACTORY;
public static EntityFactory get() {
if (FACTORY == null) {
throw new IllegalStateException("EntityFactory Not Ready Yet");
}
return FACTORY;
}
public static <T> T newInstance(Class<T> type,
Supplier<T> mapper) {
if (FACTORY != null) {
return FACTORY.newInstance(type);
}
return mapper.get();
}
}

View File

@@ -0,0 +1,16 @@
package org.hswebframework.web.api.crud.entity;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class EntityFactoryHolderConfiguration {
@Bean
public ApplicationContextAware entityFactoryHolder() {
return context -> EntityFactoryHolder.FACTORY = context.getBean(EntityFactory.class);
}
}

View File

@@ -20,7 +20,9 @@ package org.hswebframework.web.api.crud.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hswebframework.ezorm.core.param.QueryParam;
@@ -33,15 +35,20 @@ public class PagerResult<E> {
private static final long serialVersionUID = -6171751136953308027L;
public static <E> PagerResult<E> empty() {
return new PagerResult<>(0, new ArrayList<>());
return of(0, new ArrayList<>());
}
@SuppressWarnings("all")
public static <E> PagerResult<E> of(int total, List<E> list) {
return new PagerResult<>(total, list);
PagerResult<E> result;
result = EntityFactoryHolder.newInstance(PagerResult.class, PagerResult::new);
result.setTotal(total);
result.setData(list);
return result;
}
public static <E> PagerResult<E> of(int total, List<E> list, QueryParam entity) {
PagerResult<E> pagerResult = new PagerResult<>(total, list);
PagerResult<E> pagerResult = of(total, list);
pagerResult.setPageIndex(entity.getThinkPageIndex());
pagerResult.setPageSize(entity.getPageSize());
return pagerResult;

View File

@@ -0,0 +1,3 @@
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.hswebframework.web.api.crud.entity.EntityFactoryHolderConfiguration

View File

@@ -17,6 +17,7 @@ import org.hswebframework.ezorm.rdb.operator.DatabaseOperator;
import org.hswebframework.ezorm.rdb.operator.DefaultDatabaseOperator;
import org.hswebframework.web.api.crud.entity.EntityFactory;
import org.hswebframework.web.crud.annotation.EnableEasyormRepository;
import org.hswebframework.web.crud.entity.factory.EntityMappingCustomizer;
import org.hswebframework.web.crud.entity.factory.MapperEntityFactory;
import org.hswebframework.web.crud.events.CompositeEventListener;
import org.hswebframework.web.crud.events.EntityEventListener;
@@ -26,6 +27,7 @@ import org.hswebframework.web.crud.generator.DefaultIdGenerator;
import org.hswebframework.web.crud.generator.MD5Generator;
import org.hswebframework.web.crud.generator.SnowFlakeStringIdGenerator;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -52,8 +54,12 @@ public class EasyormConfiguration {
@Bean
@ConditionalOnMissingBean
public EntityFactory entityFactory() {
return new MapperEntityFactory();
public EntityFactory entityFactory(ObjectProvider<EntityMappingCustomizer> customizers) {
MapperEntityFactory factory= new MapperEntityFactory();
for (EntityMappingCustomizer customizer : customizers) {
customizer.custom(factory);
}
return factory;
}
@Bean

View File

@@ -0,0 +1,7 @@
package org.hswebframework.web.crud.entity.factory;
public interface EntityMappingCustomizer {
void custom(MapperEntityFactory factory);
}

View File

@@ -44,7 +44,7 @@ public class MapperEntityFactory implements EntityFactory, BeanFactory {
private static final DefaultMapperFactory DEFAULT_MAPPER_FACTORY = clazz -> {
String simpleClassName = clazz.getPackage().getName().concat(".Simple").concat(clazz.getSimpleName());
try {
return defaultMapper(org.springframework.util.ClassUtils.forName(simpleClassName,null));
return defaultMapper(org.springframework.util.ClassUtils.forName(simpleClassName, null));
} catch (ClassNotFoundException ignore) {
// throw new NotFoundException(e.getMessage());
}
@@ -64,7 +64,7 @@ public class MapperEntityFactory implements EntityFactory, BeanFactory {
public MapperEntityFactory() {
}
public MapperEntityFactory(Map<Class<?>, Mapper> realTypeMapper) {
public MapperEntityFactory(Map<Class<?>, Mapper<?>> realTypeMapper) {
this.realTypeMapper.putAll(realTypeMapper);
}
@@ -73,9 +73,9 @@ public class MapperEntityFactory implements EntityFactory, BeanFactory {
return this;
}
public MapperEntityFactory addCopier(PropertyCopier copier) {
Class source = ClassUtils.getGenericType(copier.getClass(), 0);
Class target = ClassUtils.getGenericType(copier.getClass(), 1);
public <S, T> MapperEntityFactory addCopier(PropertyCopier<S, T> copier) {
Class<S> source = (Class<S>) ClassUtils.getGenericType(copier.getClass(), 0);
Class<T> target = (Class<T>) ClassUtils.getGenericType(copier.getClass(), 1);
if (source == null || source == Object.class) {
throw new UnsupportedOperationException("generic type " + source + " not support");
}
@@ -91,7 +91,7 @@ public class MapperEntityFactory implements EntityFactory, BeanFactory {
return this;
}
private String getCopierCacheKey(Class source, Class target) {
private String getCopierCacheKey(Class<?> source, Class<?> target) {
return source.getName().concat("->").concat(target.getName());
}
@@ -122,11 +122,13 @@ public class MapperEntityFactory implements EntityFactory, BeanFactory {
}
if (realType == null) {
mapper = defaultMapperFactory.apply(beanClass);
}
if (!Modifier.isInterface(beanClass.getModifiers()) && !Modifier.isAbstract(beanClass.getModifiers())) {
realType = beanClass;
if (!Modifier.isInterface(beanClass.getModifiers()) && !Modifier.isAbstract(beanClass.getModifiers())) {
realType = beanClass;
}else {
mapper = defaultMapperFactory.apply(beanClass);
}
}
if (mapper == null && realType != null) {
if (logger.isDebugEnabled() && realType != beanClass) {
logger.debug("use instance {} for {}", realType, beanClass);
@@ -170,7 +172,7 @@ public class MapperEntityFactory implements EntityFactory, BeanFactory {
return (T) new HashSet<>();
}
throw new NotFoundException("can't create instance for " + beanClass);
throw new NotFoundException("error.cant_create_instance",beanClass);
}
@Override

View File

@@ -2,34 +2,36 @@ package org.hswebframework.web.crud.web;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import lombok.Getter;
import lombok.Setter;
import org.hswebframework.web.api.crud.entity.EntityFactoryHolder;
import java.io.Serializable;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseMessage<T> implements Serializable {
private static final long serialVersionUID = 8992436576262574064L;
@Schema(description = "消息提示")
protected String message;
private String message;
@Schema(description = "数据内容")
protected T result;
private T result;
@Schema(description = "状态码")
private int status;
@Schema(description = "业务码")
protected String code;
private String code;
@Schema(description = "时间戳(毫秒)")
protected Long timestamp = System.currentTimeMillis();
private Long timestamp = System.currentTimeMillis();
public ResponseMessage() {
}
public static <T> ResponseMessage<T> ok() {
return ok(null);
@@ -37,11 +39,7 @@ public class ResponseMessage<T> implements Serializable {
@SuppressWarnings("all")
public static <T> ResponseMessage<T> ok(T result) {
return (ResponseMessage) ResponseMessage.builder()
.result(result)
.status(200)
.code("success")
.build();
return of("success", null, 200, null, System.currentTimeMillis());
}
public static <T> ResponseMessage<T> error(String message) {
@@ -53,10 +51,21 @@ public class ResponseMessage<T> implements Serializable {
}
public static <T> ResponseMessage<T> error(int status, String code, String message) {
ResponseMessage<T> msg = new ResponseMessage<>();
msg.message = message;
msg.code = code;
msg.status = status;
return of(message, null, status, code, System.currentTimeMillis());
}
public static <T> ResponseMessage<T> of(String message,
T result,
int status,
String code,
Long timestamp) {
@SuppressWarnings("all")
ResponseMessage<T> msg = EntityFactoryHolder.newInstance(ResponseMessage.class, ResponseMessage::new);
msg.setMessage(message);
msg.setResult(result);
msg.setStatus(status);
msg.setCode(code);
msg.setTimestamp(timestamp);
return msg;
}

View File

@@ -7,6 +7,7 @@ import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.lang.NonNull;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MimeType;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -51,7 +52,7 @@ public class ResponseMessageWrapper extends ResponseBodyResultHandler {
private Set<String> excludes = new HashSet<>();
@Override
public boolean supports(HandlerResult result) {
public boolean supports(@NonNull HandlerResult result) {
if (!CollectionUtils.isEmpty(excludes) && result.getHandler() instanceof HandlerMethod) {
HandlerMethod method = (HandlerMethod) result.getHandler();

View File

@@ -68,5 +68,11 @@
<artifactId>r2dbc-h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -29,14 +29,15 @@ public class CustomCodecsAutoConfiguration {
CodecCustomizer jacksonDecoderCustomizer(EntityFactory entityFactory, ObjectMapper objectMapper) {
// objectMapper.setTypeFactory(new CustomTypeFactory(entityFactory));
SimpleModule module = new SimpleModule();
JsonDeserializer deserializer = new EnumDict.EnumDictJSONDeserializer();
@SuppressWarnings("all")
JsonDeserializer<Enum<?>> deserializer = new EnumDict.EnumDictJSONDeserializer();
module.addDeserializer(Enum.class, deserializer);
objectMapper.registerModule(module);
return (configurer) -> {
CodecConfigurer.DefaultCodecs defaults = configurer.defaultCodecs();
defaults.jackson2JsonDecoder(new CustomJackson2JsonDecoder(objectMapper));
defaults.jackson2JsonDecoder(new CustomJackson2JsonDecoder(entityFactory,objectMapper));
};
}

View File

@@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.util.TokenBuffer;
import org.hswebframework.web.api.crud.entity.EntityFactory;
import org.reactivestreams.Publisher;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
@@ -19,12 +20,14 @@ import org.springframework.http.codec.HttpMessageDecoder;
import org.springframework.http.codec.json.Jackson2CodecSupport;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.MimeType;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
@@ -33,11 +36,14 @@ import java.util.Map;
public class CustomJackson2JsonDecoder extends Jackson2CodecSupport implements HttpMessageDecoder<Object> {
private final EntityFactory entityFactory;
/**
* Constructor with a Jackson {@link ObjectMapper} to use.
*/
public CustomJackson2JsonDecoder(ObjectMapper mapper, MimeType... mimeTypes) {
public CustomJackson2JsonDecoder(EntityFactory entityFactory, ObjectMapper mapper, MimeType... mimeTypes) {
super(mapper, mimeTypes);
this.entityFactory = entityFactory;
}
@@ -51,7 +57,8 @@ public class CustomJackson2JsonDecoder extends Jackson2CodecSupport implements H
}
@Override
public Flux<Object> decode(Publisher<DataBuffer> input, ResolvableType elementType,
@NonNull
public Flux<Object> decode(@NonNull Publisher<DataBuffer> input, @NonNull ResolvableType elementType,
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
ObjectMapper mapper = getObjectMapper();
@@ -74,15 +81,17 @@ public class CustomJackson2JsonDecoder extends Jackson2CodecSupport implements H
}
@Override
public Mono<Object> decodeToMono(Publisher<DataBuffer> input, ResolvableType elementType,
@NonNull
public Mono<Object> decodeToMono(@NonNull Publisher<DataBuffer> input, @NonNull ResolvableType elementType,
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
return DataBufferUtils.join(input)
.map(dataBuffer -> decode(dataBuffer, elementType, mimeType, hints));
.map(dataBuffer -> decode(dataBuffer, elementType, mimeType, hints));
}
@Override
public Object decode(DataBuffer dataBuffer, ResolvableType targetType,
@NonNull
public Object decode(@NonNull DataBuffer dataBuffer, @NonNull ResolvableType targetType,
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) throws DecodingException {
try {
@@ -101,8 +110,13 @@ public class CustomJackson2JsonDecoder extends Jackson2CodecSupport implements H
Assert.notNull(elementType, "'elementType' must not be null");
MethodParameter param = getParameter(elementType);
Class<?> contextClass = (param != null ? param.getContainingClass() : null);
Type type = elementType.resolve() == null ? elementType.getType() : elementType.resolve();
Type type = elementType.resolve() == null ? elementType.getType() : elementType.toClass();
if (type instanceof Class) {
Class<?> realType = entityFactory.getInstanceType(((Class<?>) type), false);
if (realType != null) {
type = realType;
}
}
JavaType javaType = getJavaType(type, contextClass);
Class<?> jsonView = (hints != null ? (Class<?>) hints.get(Jackson2CodecSupport.JSON_VIEW_HINT) : null);
return jsonView != null ?
@@ -135,13 +149,15 @@ public class CustomJackson2JsonDecoder extends Jackson2CodecSupport implements H
// HttpMessageDecoder...
@Override
public Map<String, Object> getDecodeHints(ResolvableType actualType, ResolvableType elementType,
ServerHttpRequest request, ServerHttpResponse response) {
@NonNull
public Map<String, Object> getDecodeHints(@NonNull ResolvableType actualType, @NonNull ResolvableType elementType,
@NonNull ServerHttpRequest request, @NonNull ServerHttpResponse response) {
return getHints(actualType);
}
@Override
@NonNull
public List<MimeType> getDecodableMimeTypes() {
return getMimeTypes();
}
@@ -149,7 +165,7 @@ public class CustomJackson2JsonDecoder extends Jackson2CodecSupport implements H
// Jackson2CodecSupport ...
@Override
protected <A extends Annotation> A getAnnotation(MethodParameter parameter, Class<A> annotType) {
protected <A extends Annotation> A getAnnotation(MethodParameter parameter, @NonNull Class<A> annotType) {
return parameter.getParameterAnnotation(annotType);
}

View File

@@ -0,0 +1,53 @@
package org.hswebframework.web.starter.jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.hswebframework.web.api.crud.entity.EntityFactory;
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
import org.hswebframework.web.crud.entity.factory.MapperEntityFactory;
import org.hswebframework.web.crud.web.reactive.ReactiveQueryController;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.util.MimeType;
import reactor.core.publisher.Mono;
import java.util.Collections;
import static org.junit.Assert.*;
public class CustomJackson2JsonDecoderTest {
@Test
@SneakyThrows
public void testDecodeCustomType() {
MapperEntityFactory entityFactory = new MapperEntityFactory();
entityFactory.addMapping(QueryParamEntity.class,MapperEntityFactory.defaultMapper(CustomQueryParamEntity.class));
ObjectMapper mapper = new ObjectMapper();
CustomJackson2JsonDecoder decoder = new CustomJackson2JsonDecoder(entityFactory, mapper);
ResolvableType type = ResolvableType.forMethodParameter(
ReactiveQueryController.class.getMethod("query", QueryParamEntity.class), 0
);
DataBuffer buffer = new DefaultDataBufferFactory().wrap("{}".getBytes());
Object object = decoder.decode(buffer, type, MediaType.APPLICATION_JSON, Collections.emptyMap());
assertTrue(object instanceof CustomQueryParamEntity);
}
public static class CustomQueryParamEntity extends QueryParamEntity {
}
}