更新内容
- v1. 2021年8月6日
- 兼容 form-data/x-www-form-urlencoded/uri请求参数 三种类型的请求方式
1.背景
- 后端服务接口,针对不同客户端,请求参数名不同 ,响应结果不同
2.分析
- 只针对@RequestBody @ResponseBody接口
- 基于HttpMessageConvert实现 json 序列化 反序列化
- 重点在于jackson序列化 反序列化配置
- @JsonAlias可实现反序列化别名。为什么不用它。反序列化没有分组概念,所以这个配置无法应用到序列化上面,只能自定义别名注解
3.jackson配置
- 核心代码只有两个地方
- 序列化别名
- 反序列化别名
- 别名注解
@Target({ElementType.FIELD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ApiEntityVariableAlias {
@AliasFor("alias")
String[] value() default {};
@AliasFor("value")
String[] alias() default {};
/**
* 分组名
* 用于序列化是 按照不同的分组 序列化不同的变量名
* @return
*/
Group group() default Group.CSHARP;
@Getter
@AllArgsConstructor
enum Group{
CSHARP("c","c")
,ANDROID_APP("app","android_app")
,DEFAULT_GROUP_NAME("DEFAULT_GROUP","DEFAULT_GROUP")
;
private String value;
private String desc;
}
}
- JacksonConfig
package xxx;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@Slf4j
@Configuration
public class JacksonConfig implements Jackson2ObjectMapperBuilderCustomizer {
public static final String REQUEST_HEADER_GROUP_KEY = "xxxx";
//返回客户端分组标识,或者说是别名分组标识.
private static final Supplier<String> CLIENT_IDENTIFY_OP=()->{
ApiEntityVariableAlias.Group group = JacksonService.getGroup();
if (group == null) {
HttpServletRequest request = RequestUtils.getRequest();
String clientId = request.getHeader(REQUEST_HEADER_GROUP_KEY);
if (StringUtils.isNotBlank(clientId)) {
group = EnumUtils.findEnumDictionaryByKey(ApiEntityVariableAlias.Group.class, clientId);
}
}
if (group != null) {
return group.getValue();
}
return null;
};
@Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
SimpleModule groupPropNameModifierModule = new SimpleModule("AutoSerializePropNameModifier"
, new Version(1, 0, 0, null, null, null));
//序列化 实体属性名别名
groupPropNameModifierModule.setSerializerModifier(new BeanSerializerModifier() {
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
BeanDescription beanDesc,
List<BeanPropertyWriter> beanProperties)
{
List<BeanPropertyDefinition> properties = beanDesc.findProperties();
Map<String, BeanPropertyDefinition> annotatedPropertyDefinitionMap = properties.stream().collect(Collectors.toMap(
pd -> pd.getAccessor().getName()
, pd -> pd
//重命名下 可能有重名的情况
, (l, r) -> {
log.error("相同方法名 new:[{}:{}] old[{}:{}]", l.getName(), l.getAccessor().getName()
, r.getName(), l.getAccessor().getName()
);
return l;
}
));
Map<String, BeanPropertyDefinition> namePropertyDefinitionMap = properties.stream().collect(Collectors.toMap(
pd -> pd.getName()
, pd -> pd
//重命名下 可能有重名的情况
, (l, r) -> {
log.error("相同属性名 new:[{}:{}] old[{}:{}]", l.getName(), l.getAccessor().getName()
, r.getName(), l.getAccessor().getName()
);
return l;
}
));
for (int i = 0; i < beanProperties.size(); ++i) {
final BeanPropertyWriter writer = beanProperties.get(i);
String annotatedName = writer.getMember().getName();
BeanPropertyDefinition beanPropertyDefinition = annotatedPropertyDefinitionMap.get(annotatedName);
String name = writer.getName();
if (beanPropertyDefinition == null) {
beanPropertyDefinition = namePropertyDefinitionMap.get(name);
}
CommonUtil.isNotNull(beanPropertyDefinition,name+":"+annotatedName+" => 未找到对应属性");
BeanPropertyWriter beanPropertyWriter = new GroupNameBeanPropertyWriter(writer,beanPropertyDefinition,beanDesc, CLIENT_IDENTIFY_OP);
beanProperties.set(i, beanPropertyWriter);
}
return beanProperties;
}
});
//反序列化 别名。不区分 分组
groupPropNameModifierModule.setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
BeanDeserializerBuilder beanDeserializerBuilder = super.updateBuilder(config, beanDesc, builder);
return new BeanDeserializerBuilder(beanDeserializerBuilder){
@Override
protected Map<String, List<PropertyName>> _collectAliases(Collection<SettableBeanProperty> props) {
Map<String,List<PropertyName>> mapping = null;
AnnotationIntrospector intr = _config.getAnnotationIntrospector();
if (intr != null) {
for (SettableBeanProperty prop : props) {
List<PropertyName> aliases = intr.findPropertyAliases(prop.getMember());
//所有分组 所有别名
Class<?> entityClass = _beanDesc.getClassInfo().getRawType();
ApiEntityVariableAliasAnnotatedInfo<Field> aliasInfo = GlobalRequestBodyAdvice.parseApiEntityVariableAlias2Annotated(prop.getMember(),entityClass);
if (aliasInfo != null) {
if (aliases == null) {
aliases = new ArrayList<>();
}
List<PropertyName> aliasesF = aliases;
aliasInfo.getAliasSet().stream()
.distinct()
.forEach(d -> aliasesF.add(PropertyName.construct(d)));
}
if ((aliases == null) || aliases.isEmpty()) {
continue;
}
if (mapping == null) {
mapping = new HashMap<>();
}
mapping.put(prop.getName(), aliases);
}
}
if (mapping == null) {
return Collections.emptyMap();
}
return mapping;
}
};
}
});
jacksonObjectMapperBuilder
.annotationIntrospector(new JacksonAnnotationIntrospector() {
@Override
public List<PropertyName> findPropertyAliases(Annotated m) {
return super.findPropertyAliases(m);
}
})
.modulesToInstall(groupPropNameModifierModule)
;
}
}
- GroupNameBeanPropertyWriter :序列化配置
package xxx;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.io.SerializedString;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.PropertySerializerMap;
import lombok.Data;
import lombok.experimental.Accessors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@Data
@Accessors(chain = true)
public class GroupNameBeanPropertyWriter extends BeanPropertyWriter {
private BeanDescription beanDescription;
//对应的属性定义
private BeanPropertyDefinition beanPropertyDefinition;
//重命名后的属性名JsonProperty 默认为字段名
private SerializedString srcSerializedString;
//不同分组 对应不同序列化别名
private Map<String, List<SerializedString>> groupSerializedString = new HashMap<>();
//动态分组
private Supplier<String> groupNameSupplier;
public GroupNameBeanPropertyWriter(BeanPropertyWriter beanPropertyWriter
, BeanPropertyDefinition beanPropertyDefinition
, BeanDescription beanDescription
, Supplier<String> groupNameSupplier) {
super(beanPropertyWriter);
CommonUtil.isNotNull(beanDescription,"beanDescription");
CommonUtil.isNotNull(beanPropertyDefinition,"beanPropertyDefinition");
this.beanDescription = beanDescription;
this.beanPropertyDefinition = beanPropertyDefinition;
this.srcSerializedString = super._name;
this.groupNameSupplier = groupNameSupplier;
parseGroupSerializedString(this);
}
public String getGroupName() {
return getGroupSerializedString().getValue();
}
public SerializedString getGroupSerializedString() {
String groupName = null;
List<SerializedString> alias = null;
if (this.groupNameSupplier == null
|| StringUtils.isBlank(groupName = groupNameSupplier.get())
|| CollectionUtils.isEmpty(alias = groupSerializedString.get(groupName))
) {
return srcSerializedString;
}
return alias.get(0);
}
/**
解析序列化属性别名
*
* @param beanPropertyWriter
*/
private void parseGroupSerializedString(BeanPropertyWriter beanPropertyWriter) {
String fn=null;
AnnotatedField field = beanPropertyDefinition.getField();
if (field != null) {
fn=field.getName();
}else{
fn=beanPropertyDefinition.getInternalName();
}
if (StringUtils.isNotBlank(fn)) {
ApiEntityVariableAliasAnnotatedInfo<Field> groupAliases = GlobalRequestBodyAdvice.parseApiEntityVariableAlias2Annotated(fn
, this.beanDescription.getClassInfo().getAnnotated());
if (groupAliases != null) {
groupAliases.getAlias().forEach((groupName, aliases) -> {
if (!CollectionUtils.isEmpty(aliases)) {
List<SerializedString> groupSerializedString = aliases.stream().filter(StringUtils::isNotBlank)
.map(SerializedString::new)
.collect(Collectors.toList());
this.groupSerializedString.put(groupName, groupSerializedString);
}
});
}
}
}
@Override
public void serializeAsField(Object bean, JsonGenerator gen,
SerializerProvider prov) throws Exception {
// inlined 'get()'
final Object value = (_accessorMethod == null) ? _field.get(bean)
: _accessorMethod.invoke(bean, (Object[]) null);
SerializableString _name = getGroupSerializedString();
// Null handling is bit different, check that first
if (value == null) {
if (_nullSerializer != null) {
gen.writeFieldName(_name);
_nullSerializer.serialize(null, gen, prov);
}
return;
}
// then find serializer to use
JsonSerializer<Object> ser = _serializer;
if (ser == null) {
Class<?> cls = value.getClass();
PropertySerializerMap m = _dynamicSerializers;
ser = m.serializerFor(cls);
if (ser == null) {
ser = _findAndAddDynamic(m, cls, prov);
}
}
// and then see if we must suppress certain values (default, empty)
if (_suppressableValue != null) {
if (MARKER_FOR_EMPTY == _suppressableValue) {
if (ser.isEmpty(prov, value)) {
return;
}
} else if (_suppressableValue.equals(value)) {
return;
}
}
// For non-nulls: simple check for direct cycles
if (value == bean) {
// three choices: exception; handled by call; or pass-through
if (_handleSelfReference(bean, gen, prov, ser)) {
return;
}
}
gen.writeFieldName(_name);
if (_typeSerializer == null) {
ser.serialize(value, gen, prov);
} else {
ser.serializeWithType(value, gen, prov, _typeSerializer);
}
}
@Override
public void serializeAsOmittedField(Object bean, JsonGenerator gen,
SerializerProvider prov) throws Exception {
if (!gen.canOmitFields()) {
gen.writeOmittedField(getGroupSerializedString().getValue());
}
}
@Override
public String toString() {
SerializedString groupSerializedString = getGroupSerializedString();
return "["+(groupSerializedString==this.srcSerializedString?"notGroup":groupNameSupplier.get())
+" : "
+groupSerializedString.getValue()+"] "
+super.toString()
;
}
}
4.测试
- 反序列化 别名
- 序列化分组别名
5. v1更新
-
适用范围:通过
ModelAttributeMethodProcessor
解析的请求参数。例如,请求参数是实体类
-
原理:
HandlerMethodArgumentResolver
。通过重写DataBinder.addBindValues
实现别名参数绑定 -
配置
-
这里的别名配置可以通过上面配置的jackson获取
-
也可以加入自定义的别名配置
@Configuration @Slf4j public class CustomWebMvcRegistrations implements WebMvcRegistrations { @Autowired private ObjectMapper objectMapper; public static final Field BeanDeserializerBase_beanProperties_field=ReflectionUtils.findField(BeanDeserializerBase.class,"_beanProperties", BeanPropertyMap.class); @Override public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() { return new RequestMappingHandlerAdapter(){ @Override protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods) throws Exception { return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer()){ /** * Returns an instance of {@link ExtendedServletRequestDataBinder}. */ @Override protected ServletRequestDataBinder createBinderInstance( @Nullable Object target, String objectName, NativeWebRequest request) throws Exception { return new ExtendedServletRequestDataBinder(target, objectName){ @Override protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) { //添加别名映射 super.addBindValues(mpvs, request); replaceAlias(mpvs, request); } private void replaceAlias(MutablePropertyValues mpvs, ServletRequest request){ Object t = getTarget(); CommonUtil.isNotNull(t,"Binder target"); Class<?> targetClass = t.getClass(); JavaType javaType = objectMapper.getTypeFactory().constructType(targetClass); AtomicReference<Throwable> causeRef = new AtomicReference<>(); Function<String,String> aliasToFieldNameOp=null; if(objectMapper.canDeserialize(javaType, causeRef)){ try { JsonDeserializer<Object> deserializer = objectMapper.getDeserializationContext().findNonContextualValueDeserializer(javaType); if(BeanDeserializerBase_beanProperties_field != null && deserializer !=null && BeanDeserializerBase.class.isAssignableFrom(deserializer.getClass())){ ReflectionUtils.makeAccessible(BeanDeserializerBase_beanProperties_field); BeanPropertyMap beanProperties = (BeanPropertyMap)ReflectionUtils.getField(BeanDeserializerBase_beanProperties_field, deserializer); //依赖于jackson aliasToFieldNameOp=aliasOrFieldName->{ if (!beanProperties.hasAliases()) { return null; } SettableBeanProperty settableBeanProperty = beanProperties.find(aliasOrFieldName); return settableBeanProperty==null?null:settableBeanProperty.getName(); }; } } catch (JsonMappingException e) { log.warn("未找到{}类的反序列化实例", targetClass.getName(), e); } } //自定义 if (aliasToFieldNameOp == null) { aliasToFieldNameOp= aliasOrFieldName->{ /*ApiEntityVariableAliasAnnotatedInfo<Field> aliasInfo = GlobalRequestBodyAdvice.parseApiEntityVariableAlias2Annotated(aliasOrFieldName,targetClass); if (aliasInfo!=null && !CollectionUtils.isEmpty(aliasInfo.getAliasSet())) { aliasInfo.getAliasSet().stream().forEach(aliasName->{ }); }*/ return null; }; } PropertyValue[] pvArray = mpvs.getPropertyValues(); for (PropertyValue pv : pvArray) { String field = PropertyAccessorUtils.canonicalPropertyName(pv.getName()); if (StringUtils.isNotBlank(field) && aliasToFieldNameOp != null) { String actualFieldName = aliasToFieldNameOp.apply(field); if (StringUtils.isNotBlank(actualFieldName) && getPropertyAccessor().isWritableProperty(actualFieldName) && !mpvs.contains(actualFieldName)) { mpvs.add(actualFieldName, pv.getValue()); mpvs.removePropertyValue(pv); } } } } }; } }; } }; } }
-
文件上传 : 在参数绑定过程中,可以看到上传文件的绑定流程。这里不想在实体类 或者 请求参数上添加额外的接收上传文件参数,可以直接从request中获取,这里提供一种方式 点击
-