基于jackson实现 请求参数别名 和 响应数据别名 支持RequestBody和ModelAttribute实体

更新内容

  1. 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更新

  1. 适用范围:通过 ModelAttributeMethodProcessor解析的请求参数。例如,请求参数是实体类
    在这里插入图片描述

  2. 原理:HandlerMethodArgumentResolver 。通过重写 DataBinder.addBindValues实现别名参数绑定

  3. 配置

    • 这里的别名配置可以通过上面配置的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);
                                              }
                                          }
                                      }
                                  }
                              };
                          }
                      };
                  }
              };
          }
      }
      
    1. 文件上传 : 在参数绑定过程中,可以看到上传文件的绑定流程。这里不想在实体类 或者 请求参数上添加额外的接收上传文件参数,可以直接从request中获取,这里提供一种方式 点击

      在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是 Android 使用极光推送绑定别名的代码示例: ```java import cn.jpush.android.api.JPushInterface; // 绑定别名 JPushInterface.setAlias(context, sequence, alias); // 解绑别名 JPushInterface.deleteAlias(context, sequence); ``` 其中,`context` 参数为当前上下文对象,`sequence` 参数请求码,`alias` 参数为需要绑定的别名。 注意事项: - 绑定别名时,如果已经绑定了别名,则会覆盖之前的别名; - 解绑别名时,如果当前设备没有绑定别名,则不会有任何影响。 另外,在使用极光推送时,需要在 AndroidManifest.xml 文件中添加以下权限: ```xml <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="cn.jpush.android.permission.RECEIVE_MSG" /> <uses-permission android:name="cn.jpush.android.permission.READ_PHONE_STATE" /> <uses-permission android:name="cn.jpush.android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="cn.jpush.android.permission.VIBRATE" /> <uses-permission android:name="cn.jpush.android.permission.RECEIVE_BOOT_COMPLETED" /> ``` 并且,需要在 AndroidManifest.xml 文件中添加以下服务和接收器: ```xml <!-- 极光推送服务 --> <service android:name="cn.jpush.android.service.PushService" android:enabled="true" android:exported="false" > <intent-filter> <action android:name="cn.jpush.android.intent.REGISTER" /> <action android:name="cn.jpush.android.intent.REPORT" /> <action android:name="cn.jpush.android.intent.PushService" /> <action android:name="cn.jpush.android.intent.PUSH_TIME" /> </intent-filter> </service> <!-- 极光推送接收器 --> <receiver android:name="cn.jpush.android.service.PushReceiver" android:enabled="true" > <intent-filter android:priority="1000"> <action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED_PROXY" /> <category android:name="${applicationId}" /> </intent-filter> <intent-filter> <action android:name="cn.jpush.android.intent.REGISTRATION" /> <category android:name="${applicationId}" /> </intent-filter> <intent-filter> <action android:name="cn.jpush.android.intent.UNREGISTRATION" /> <category android:name="${applicationId}" /> </intent-filter> <intent-filter> <action android:name="cn.jpush.android.intent.MESSAGE_RECEIVED" /> <category android:name="${applicationId}" /> <data android:scheme="package" /> </intent-filter> <intent-filter> <action android:name="cn.jpush.android.intent.NOTIFICATION_OPENED" /> <category android:name="${applicationId}" /> </intent-filter> <intent-filter> <action android:name="cn.jpush.android.intent.ACTION_RICHPUSH_CALLBACK" /> <category android:name="${applicationId}" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.USER_PRESENT" /> <category android:name="${applicationId}" /> </intent-filter> <intent-filter> <action android:name="android.net.conn.CONNECTIVITY_CHANGE" /> </intent-filter> </receiver> <!-- 极光推送唤醒接收器 --> <receiver android:name="cn.jpush.android.service.AlarmReceiver" android:enabled="true" /> ``` 以上是 Android 使用极光推送绑定别名的代码示例及注意事项。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值