spring boot 的接口form入参-字段映射别名

23 篇文章 0 订阅
12 篇文章 0 订阅

接口开发,入参是使用的蟒蛇式(如:psn_name),而后端接收使用的是驼峰式(如psnName)。并且入参很多,使用spring 的 @RequestParam 只适用少量入参,需要自动进行参数转换。

参考

如何在绑定Spring MVC命令对象时自定义参数名称?
spring mvc给参数起别名
SpringMVC请求参数别名设置
SpringMVC 通过post接收form参数或者json参数
Spring boot 参数别名处理
springboot中使用servlet

实现代码

接口

/**
 * 如果是实体类,并且用到了注解ReqParamAs,请实现本类
 *
 * @author z.y
 * @version v1.0
 * @date 2022/6
 */
public interface ReqParamBean { }

注解

import java.lang.annotation.*;
/**请求的字段别名设置,通过自定义spring属性编辑器解决
 * @author z.y
 * @version v1.0
 * @date 2022/6
 */
@Target (ElementType.FIELD)
@Retention (RetentionPolicy.RUNTIME)
@Documented
public @interface ReqParamAs {
    String value();
}

自定义spring属性编辑器

import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.web.servlet.mvc.method.annotation.ExtendedServletRequestDataBinder;

import javax.servlet.ServletRequest;
import java.util.Map;

/**
 * 请求的字段别名-数据绑定处理,通过自定义spring属性编辑器解决
 * @author z.y
 * @version v1.0
 * @date 2022/6
 */
public class ReqParamAsDataBinder extends ExtendedServletRequestDataBinder {
    private static final Logger logger = LoggerFactory.getLogger(ReqParamAsDataBinder.class);
    private final Map<String, String> cacheMap;
    public ReqParamAsDataBinder(Object target,String objectName,Map<String, String> asMap){
        super(target,objectName);
        this.cacheMap = asMap;
    }
    /**
     * 复写addBindValues方法
     * @param mpv 这里面存的就是请求参数的key-value对
     * @param req 请求本身, 这里没有用到
     */
    @Override
    protected void addBindValues(@NotNull MutablePropertyValues mpv, @NotNull ServletRequest req) {
        super.addBindValues(mpv, req);
        Object obj;
        PropertyValue pv;
        logger.info("字段别名-缓存,{}",cacheMap);
        for (Map.Entry<String, String> entry : cacheMap.entrySet()) {
            String alias = entry.getKey(),fieldName = entry.getValue();
            if(mpv.contains(alias)){
                pv = mpv.getPropertyValue(alias);
                if( null == pv ){ continue; }
                obj = pv.getValue();
                logger.debug("字段别名-数据绑定处理,{}<>{},{}",alias,fieldName,obj);
                // 给原始字段赋值
                mpv.add(fieldName, obj);
            }
        }
    }
}

参数解析器

import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor;

import javax.servlet.ServletRequest;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 请求的字段别名-参数解析器,通过自定义spring属性编辑器解决
 *
 * @author z.y
 * @version v1.0
 * @date 2022/6
 */
public class ReqParamAsProcessor extends ServletModelAttributeMethodProcessor {
    private static final Map<Class<?>,Map<String, String>> PARAM_CACHE_MAP = new ConcurrentHashMap<>();
    private final ApplicationContext context;
    public ReqParamAsProcessor(ApplicationContext applicationContext){
        super(true);
        this.context = applicationContext;
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // 接口方法不含 注解 RequestParam、入参不是 简单参数、并且参数对象内的字段包含自定义注解 RequestParam
        return !parameter.hasParameterAnnotation(RequestParam.class)
            && !parameter.hasParameterAnnotation(RequestBody.class)
            && !BeanUtils.isSimpleProperty(parameter.getParameterType())
            && Arrays.stream(parameter.getParameterType().getDeclaredFields())
                .anyMatch(field -> field.getAnnotation(ReqParamAs.class) != null);
    }

    @Override
    protected void bindRequestParameters(@NotNull WebDataBinder binder, @NotNull NativeWebRequest request) {
        Map<String, String> asMap = cacheMap(Objects.requireNonNull(binder.getTarget()).getClass());
        ReqParamAsDataBinder dataBinder = new ReqParamAsDataBinder(binder.getTarget(),binder.getObjectName(),asMap);
        RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class);
        Objects.requireNonNull(adapter.getWebBindingInitializer()).initBinder(dataBinder);
        dataBinder.bind(Objects.requireNonNull(request.getNativeRequest(ServletRequest.class)));
        super.bindRequestParameters(binder, request);
    }
    private Map<String, String> cacheMap(Class<?> target){
        if(PARAM_CACHE_MAP.containsKey(target)){
            return PARAM_CACHE_MAP.get(target);
        }
        Map<String, String> map = analyzeClass(target,"","");
        PARAM_CACHE_MAP.put(target,map);
        return map;
    }
    private Map<String, String> analyzeClass(Class<?> target,String prtAs,String prtField){
        Field[] fields = target.getDeclaredFields();
        Map<String, String> map = new HashMap<>(fields.length);
        ReqParamAs rpa;
        boolean boo;
        String as;
        for (Field field : fields) {
            boo = (null == (rpa = field.getAnnotation(ReqParamAs.class)) || rpa.value().isEmpty());
            as = boo ? field.getName() : rpa.value();
            if( isExtendBean(field.getType().getInterfaces()) ){
                //如果字段类实现了自定义接口,就认为是自定义是对象类,继续解析字段
                // 通过自定义注解在类上面使用,在这里判断也可以
                map.putAll(analyzeClass(field.getType(),prtAs + as + '.',prtField + field.getName() + '.'));
            }else if( !boo || !"".equals(prtAs)){
               // 只绑定需要的映射
                map.put(prtAs + as,prtField + field.getName());
            }
        }
        return map;
    }
    private boolean isExtendBean(Class<?>[] interfaces){
        for (Class<?> face : interfaces) {
            if(face == ReqParamBean.class){ return true; }
        }
        return false;
    }
}

注入spring


import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import java.util.ArrayList;
import java.util.List;

/**
 * 请求的字段别名-参数解析器 添加到第一个参数解析器中的bean配置,通过自定义spring属性编辑器解决
 *
 * @author z.y
 * @version v1.0
 * @date 2022/6
 */
@Configuration
public class ReqParamAsConfig {
    private ApplicationContext applicationContext;
    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Bean
    public ReqParamAsProcessor reqParamAsProcessor(){
        return new ReqParamAsProcessor(applicationContext);
    }
    @Bean
    public BeanPostProcessor beanPostProcessor(){
        return new BeanPostProcessor() {
            @Override
            public Object postProcessBeforeInitialization(@NotNull Object bean, @NotNull String beanName) throws BeansException {
                return bean;
            }
            @Override
            public Object postProcessAfterInitialization(@NotNull Object bean, @NotNull String beanName) throws BeansException {
                if(bean instanceof RequestMappingHandlerAdapter){
                    RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter)bean;
                    List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
                    resolvers.add(reqParamAsProcessor());
                    if(adapter.getArgumentResolvers() != null){
                        resolvers.addAll(adapter.getArgumentResolvers());
                    }
                    adapter.setArgumentResolvers(resolvers);
                }
                return bean;
            }
        };
    }
}

测试代码

实体类-使用别名注解

import ...annotation.ReqParamAs;
import ...annotation.ReqParamBean;

/**
 * 测试类
 * @author z.y
 * @version v1.0
 * @date 2022/6
 */
public class TestAsBean {
    private String name;
    private Integer age;
    @ReqParamAs ("psn_name")
    private String psnName;
    private Integer psnAge;
    @ReqParamAs (value = "my_dog")
    private Dog dog;
    public static class Dog implements ReqParamBean {
        private String dogName;
    	@ReqParamAs ("dog_age")
        private Integer dogAge;
        public String getDogName() { return dogName; }
        public void setDogName(String dogName) { this.dogName = dogName; }
        public Integer getDogAge() { return dogAge; }
        public void setDogAge(Integer dogAge) { this.dogAge = dogAge; }
        @Override
        public String toString() {
            return "{dogName='" + dogName + "', dogAge=" + dogAge + '}';
        }
    }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
    public String getPsnName() { return psnName; }
    public void setPsnName(String psnName) { this.psnName = psnName; }
    public Integer getPsnAge() { return psnAge; }
    public void setPsnAge(Integer psnAge) { this.psnAge = psnAge; }
    public Dog getDog() { return dog; }
    public void setDog(Dog dog) { this.dog = dog; }
    @Override
    public String toString() {
        return "{name='" + name + "', age=" + age + ", psnName='" + psnName + "', psnAge=" + psnAge + ", dog=" + dog + '}';
    }
}

测试接口

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author z.y
 * @version v1.0
 * @date 2022/6
 */
@RestController
@RequestMapping ("test/as")
public class TestAsController {
    private static final Logger logger = LoggerFactory.getLogger(TestAsController.class);
    @PostMapping (value = "root")
    public TestAsBean root(TestAsBean bean){
        logger.info("测试字段别名,{}",bean);
        bean.setName("你说啥");
        return bean;
    }
}

测试记录

未使用别名

http://localhost:8092/test/as/root
age=1&dog.dogAge=2&dog.dogName=%E7%AD%89%E5%BE%85&name=%E5%95%8A%E5%95%8A&psnAge=4&psnName=%E5%A4%9A%E5%A4%A7

2022-06-15 18:22:39.211 - [] - [http-nio-8092-exec-5] INFO  ...controller.TestAsController.root(20) - 测试字段别名,{name='啊啊', age=1, psnName='多大', psnAge=4, dog={dogName='等待', dogAge=2}}

在这里插入图片描述

使用别名

http://localhost:8092/test/as/root
age=1&my_dog.dog_age=2&dog.dogName=%E7%AD%89%E5%BE%85&name=%E5%95%8A%E5%95%8A&psnAge=4&psn_name=%E5%A4%9A%E5%A4%A7

2022-06-17 14:29:39.782 - INFO  .annotation.ReqParamAsDataBinder.addBindValues(37) - 字段别名-缓存,{my_dog.dog_age=dog.dogAge, psn_name=psnName, my_dog.dogName=dog.dogName}
2022-06-17 14:29:39.783 - DEBUG .annotation.ReqParamAsDataBinder.addBindValues(44) - 字段别名-数据绑定处理,my_dog.dog_age<>dog.dogAge,2
2022-06-17 14:29:39.785 - DEBUG .annotation.ReqParamAsDataBinder.addBindValues(44) - 字段别名-数据绑定处理,psn_name<>psnName,多大
2022-06-17 14:29:40.057 - INFO  .controller.TestAsController.root(20) - 测试字段别名,{name='啊啊', age=1, psnName='多大', psnAge=4, dog={dogName='等待', dogAge=2}}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

问题-已解决

当前级联别名,暂未处理,记录debug截图,有时间调整代码逻辑实现。
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值