众所周知,spring mvc 在进行参数绑定的时候。前端请求的参数名称与后端定义的类名称是一一对应的。比如:请求参数有一个 name 值传到后端。后端只需要定义一个类,然后类里面声明一个 name 属性。在发送请求的时候 spring mvc 就会自动把 name 的值填充到这个定义的类里面。现在遇到一个问题就是比如前端定义一个 goods_name (商品名称),如果后端也声明一个 goods_name 这样固然可以把属性添加进去。但是这样操作就不够优雅。
我遇到这个问题最开始的想法是通过 spring 的自动类型转换来做的。但是每个自动类型转换都尝试的一下发现都不行。最后使用 spring mvc 的请求参数绑定 (HandlerMethodArgumentResolver) 来做这样事。
思路如下:
在进行请求参数绑定的时候它会根据请求方法里面的请求参数创建一个对象,然后通过 spring 的依赖注入把请求对象里面的数据注入到创建的对象中。
我的思路就是在上面 2 个步骤之间通过自定义参数解析器把参数绑定进去。我首先自定义一个请求参数绑定解析器,然后在类里面声明一个注解,请求对象里面的属性名称与对应类属性的名称对应起来。在创建对象之后,从请求对象内拿出对应类属性的值然后通过反射把值 setter 进去。
具体代码如下:
1、Key.java
自定义注解类,关联 request 请求对象中的属性名称与类属性
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Key {
String value() default "";
}
2、EnableKey
自定义注解类,用于标记声明请求参数绑定解析器来解析对应 url 请求的请求参数的解析。
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableKey {
}
3、CustomModelAttributeMethodProcessor.java
自定义参数解析类,用于自定义类名称参数绑定。
public class CustomModelAttributeMethodProcessor extends ModelAttributeMethodProcessor {
public CustomModelAttributeMethodProcessor(boolean annotationNotRequired) {
super(annotationNotRequired);
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(EnableKey.class);
}
@Override
protected Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {
Object result = super.createAttribute(attributeName, parameter, binderFactory, request);
Class<?> targetClazz = result.getClass();
for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(targetClazz)) {
//skip class
if (pd.getName().equals("class")){
continue;
}
TypeDescriptor td = build(targetClazz, pd);
Key key = td.getAnnotation(Key.class);
if(key != null && StringUtils.isNotBlank(key.value())){
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(servletRequest);
addBindValues(mpvs, servletRequest);
PropertyValue propertyValue = mpvs.getPropertyValue(key.value());
if(propertyValue != null){
String value = (String) propertyValue.getValue();
pd.getWriteMethod().invoke(result, value);
}
}
}
return result;
}
private TypeDescriptor build(Class<?> beanClz, PropertyDescriptor pd) {
Property p = new Property(beanClz, pd.getReadMethod(), pd.getWriteMethod(), pd.getName());
return new TypeDescriptor(p);
}
protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
Map<String, String> uriVars = (Map<String, String>) request.getAttribute(attr);
if (uriVars != null) {
uriVars.forEach((name, value) -> {
if (mpvs.contains(name)) {
if (logger.isWarnEnabled()) {
logger.warn("Skipping URI variable '" + name +
"' because request contains bind value with same name.");
}
}
else {
mpvs.addPropertyValue(name, value);
}
});
}
}
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
Assert.state(servletRequest != null, "No ServletRequest");
ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
servletBinder.bind(servletRequest);
}
}
4、WebMvcConfig.java
spring mvc 配置类,把自定义参数解析器添加到 spring mvc 的参数解析器列表当中。
@EnableWebMvc
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new CustomModelAttributeMethodProcessor(true));
}
}
5、User.java
定义 POJO 类用于接收请求参数。
@Data
public class User {
private String id;
private String name;
private boolean flag;
@Key("bank_type")
private String bankType;
}
6、UserController.java
controller 测试类。
@RestController
@RequestMapping(value = "test")
public class UserController {
@RequestMapping(value = "/user/add")
public void add(@EnableKey User user){
System.out.println(user);
}
}
使用 postman 发送请求:
控制台需要 User 对象:
这样请求对象为 bank_type 就可以绑定到后台 bankType 属性上面了。