【Spring】请求参数封装成POJO时,如何取别名?

本文介绍了一种在SpringMVC项目中,通过自定义注解和数据绑定器解决GET请求中POJO封装参数命名不一致的问题,使得可以为Java变量的驼峰命名提供HTTP接口约定的下划线命名别名绑定。
摘要由CSDN通过智能技术生成

背景

在SpringMVC项目中,对于HTTP的GET请求,我们常在Controller方法的入参中,用@RequestParam指定请求参数名。但当请求参数较多时,会导致参数列表很长,一般建议用POJO将请求参数封装起来。然而,@RequestParam并不支持标注在类属性上,无法给POJO中封装的请求参数指定别名,请求参数名只能与属性名相同。这在实际项目中有很大不便,比如HTTP接口格式通常约定使用下划线命名,但Java变量规范使用驼峰命名,需要对POJO封装的请求参数取别名,如下所示。

@Data
public class Pet {

    // @RequestParam("nick_name") 不支持
    private String nickName;
}
@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/pet")
    public String testPet(Pet pet) {
        // "/test/pet?nickName=Tom"  -> pet.nickName is "Tom"
        // "/test/pet?nick_name=Tom" -> pet.nickName is null
        return pet.toString();
    }
}

思路

通过调试可知,当Controller方法的参数为自定义类型(POJO)时,对应的参数解析器为ServletModelAttributeMethodProcessor。它通过一个WebDataBinder(实际上用的是子类ExtendedServletRequestDataBinder)将请求参数的值绑定到POJO中对应的属性里去。ExtendedServletRequestDataBinder的继承关系如下。

WebDataBinder
  └── ServletRequestDataBinder
      └── ExtendedServletRequestDataBinder

其中,ServletRequestDataBinder中有一个钩子方法addBindValues(),允许在真正执行绑定操作前添加绑定值(添加到入参mpvs中)。我们可以在这个方法中,将POJO的属性名作为绑定值添加进去,这样Spring就能识别到该属性并将请求参数与之绑定。例如,在上面的例子中,我们可以通过自定义注解将nick_name与POJO的属性nickName相关联,当请求参数中有nick_name=Tom时,mpvs中会记录nick_name->Tom对应关系,我们将nickName->Tom也添加到mpvs中,这样Spring就能将Tom与POJO的属性nickName绑定了。

public class ServletRequestDataBinder extends WebDataBinder {

    // ...

	/**
	 * Extension point that subclasses can use to add extra bind values for a
	 * request. Invoked before {@link #doBind(MutablePropertyValues)}.
	 * The default implementation is empty.
	 * @param mpvs the property values that will be used for data binding
	 * @param request the current request
	 */
	protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
	}

    // ...
}

代码实现

以下是上述思路的具体代码实现。

/**
 * 自定义注解,用于标注请求参数名
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ParamName {

    /**
     * 请求参数名
     */
    String value();
}
/**
 * 自定义数据绑定器,用于处理 {@link ParamName} 标注的请求参数名
 */
public class ParamNameDataBinder extends ExtendedServletRequestDataBinder {

    public ParamNameDataBinder(Object target, String objectName) {
        super(target, objectName);
    }

    @Override
    protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
        super.addBindValues(mpvs, request);

        // 获取要绑定的目标对象,即封装了请求参数的实体
        Object target = getTarget();
        if (target == null) {
            return;
        }
        Class<?> targetClass = target.getClass();
        // 通过反射获取该实体类的所有属性
        Field[] fields = targetClass.getDeclaredFields();
        for (Field field : fields) {
            ParamName paramName = field.getAnnotation(ParamName.class);
            // 如果属性上标注了请求参数名,并且请求中确实传了该参数的值(mpvs中有),
            // 则将该属性的名称加入mpvs中,这样在后续处理中就能将参数值正确绑定到该属性了
            if (paramName != null) {
                Object paramValue = mpvs.get(paramName.value());
                if (paramValue != null) {
                    mpvs.add(field.getName(), paramValue);
                }
            }
        }
    }
}
/**
 * 自定义数据绑定器的创建工厂
 */
public class ParamNameDataBinderFactory extends ServletRequestDataBinderFactory {

    public ParamNameDataBinderFactory(List<InvocableHandlerMethod> binderMethods, WebBindingInitializer initializer) {
        super(binderMethods, initializer);
    }

    @Override
    protected ServletRequestDataBinder createBinderInstance(Object target, String objectName, NativeWebRequest request)
            throws Exception {
        // 使用自定义数据绑定器
        return new ParamNameDataBinder(target, objectName);
    }
}
/**
 * 自定义 HandlerAdapter
 */
public class ParamNameHandlerAdapter extends RequestMappingHandlerAdapter {

    @Override
    protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)
            throws Exception {
        // 使用自定义数据绑定器的创建工厂
        return new ParamNameDataBinderFactory(binderMethods, getWebBindingInitializer());
    }
}
/**
 * 自定义 Web MVC 配置
 */
@Configuration
public class CustomWebMvcConfig extends WebMvcConfigurationSupport {

    @Override
    protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
        // 使用自定义的 HandlerAdapter
        return new ParamNameHandlerAdapter();
    }
}

使用上也很简单,直接用自定义注解@ParamName在POJO的属性上标注别名,即可生效。

@Data
public class Pet {

    @ParamName("nick_name")
    private String nickName;
}
@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/pet")
    public String testPet(Pet pet) {
        // "/test/pet?nickName=Tom"  -> pet.nickName is "Tom"
        // "/test/pet?nick_name=Tom" -> pet.nickName is "Tom"
        return pet.toString();
    }
}

总结

SpringMVC中,当请求参数封装成POJO时,参数名必须与POJO属性名相同,无法通过@RequestParam取别名。我们可以通过自定义注解以及自定义数据绑定器的方式,实现给请求参数取别名,以满足实际项目需求。

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值