自定义XXXModelAttributeResolver解决请求下划线,但是接收对象为驼峰形式
1. 前言
在SpringBoot中@ModelAttribute 可以绑定请求参数到 Java 对象上,例如:
@PostMapping("add")
public ResponseMessage add(@ModelAttribute UserInfo userInfo){
但是如果请求header是Content-Type:application/x-www-form-urlencoded, 且请求参数是下划线的形式,比如: user_id:1
此时@ModelAttribute则没法把user_id:1 这个值绑定到你的对象 Integer userId;上
那么为了解决这个问题,我们可以自定义一个XXXModelAttributeResolver,然后判断自定义的注解,把注解映射的下划线参数绑定到对象参数上
下面则是具体的操作步骤
2. 自定义注解@SnakeModelAttribute、@SnakeParam
@SnakeModelAttribute
@SnakeModelAttribute 的作用是代替@ModelAttribute, 当我们拿到值和对象进行绑定时,通过这个注解进行判定。
import java.lang.annotation.*;
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SnakeModelAttribute {
}
@SnakeParam
@SnakeParam的作用是,进行绑定的时候,拿到驼峰属性对应下划线请求参数。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SnakeParam {
String name() default "";
}
使用时像这样:
@SnakeParam(name = "name")
private String topicId;
3. 自定义一个SnakeModelAttributeResolver
SnakeModelAttributeResolver继承了ModelAttributeMethodProcessor,其作用是为了实现自定义的属性绑定逻辑。
代码执行时大致的流程像这样:

代码如下,等等会说明关键高亮部分代码的含义:
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.ServletRequestParameterPropertyValues;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.annotation.ModelAttributeMethodProcessor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* SnakeModelAttributeResolver
* application/x-www-form-urlencoded 请求时,下划线参数转驼峰
*/
public class SnakeModelAttributeResolver extends ModelAttributeMethodProcessor {
public SnakeModelAttributeResolver() {
super(false);
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
//有ModelAttribute 注解时,会进入到resolveArgument转换
return parameter.hasParameterAnnotation(SnakeModelAttribute.class);
}
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(servletRequest);
Object target = binder.getTarget();
if (target != null) {
Class<?> targetClass = target.getClass();
Map<String, String> snakeToCamel = new HashMap<>();
for (Field field : targetClass.getDeclaredFields()) {
SnakeParam annotation = field.getAnnotation(SnakeParam.class);
if (annotation != null) {
String snakeName = annotation.name();
if (!snakeName.isEmpty()) {
snakeToCamel.put(snakeName, field.getName());
}
}
}
List<PropertyValue> updated = new ArrayList<>();
for (PropertyValue pv : mpvs.getPropertyValueList()) {
String originalName = pv.getName();
if (snakeToCamel.containsKey(originalName)) {
updated.add(new PropertyValue(snakeToCamel.get(originalName), pv.getValue()));
} else {
updated.add(pv);
}
}
mpvs = new MutablePropertyValues(updated);
}
binder.bind(mpvs);
}
}
3.1 SnakeModelAttributeResolver
super(false); 意味着初始化SnakeModelAttributeResolver的时候,对内部属性:annotationNotRequired 设置为false,
private final boolean annotationNotRequired;
这个属性在父类用来判断是否要使用这个类进行处理
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
3.2 supportsParameter
这个比较简单,就是判断绑定的这个对象上是否使用了自定义注解@SnakeModelAttribute
3.3 bindRequestParameters
这里做的处理就是,判断对象的属性上是否使用了@SnakeParam注解,如果有且不为空,那么则增加对应的绑定关系到snakeToCamel中
4. 对WebMvcConfigurer增加自定义的参数解析器
实现WebMvcConfigurer接口,然后重写addArgumentResolvers,增加SnakeModelAttributeResolver即刻
@Configuration
public class ApplicationResolverConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new SnakeModelAttributeResolver());
}
}
5. Q&A
5.1 为什么要自定义注解@SnakeModelAttribute,使用原有的@ModelAttribute判断不信吗?
不行的,因为项目在启动后,默认的ServletModelAttributeMethodProcessor优先级更高,会使用这个进行参数绑定,从而代码不会执行到你自定义的SnakeModelAttributeResolver中