通常情况下, 后端的变量命名方式都是驼峰命名法(Camel Case)
, 而前端就不一定了, 有的用的是Snake Case
(即多个单词之间用_
隔开)。 在不改变命名的情况下, 如何把不同名字的参数进行绑定呢?比如user_name
绑定到userName
上。
@RequestParam("user_name") String userName
这种形式, 但是当参数过多时也不优雅如下一个Controller
参数是通过一个实体Test
接收的, 但是要接收的参数名称跟实体类的字段名称对应不上
// Controller
@RestController
@RequestMapping(value = "/test/")
public class TestController {
@RequestMapping(value = "index.htm")
public String home(Test test) {
return "It works!";
}
}
// Test实体
public class Test {
// 要接收参数 user_name
private String userName;
// 要接收参数 home_address
private String address;
}
如上, Test
中的字段分别叫userName
和address
, 但参数却为user_name
和home_address
. 接下来我们就要实现这种绑定关系
定义如下的注解, 注解只允许添加到字段上, 需要指明别名列表(即把参数中的别名绑定到实体字段上)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValueFrom {
/**
* 参数名(别名)列表
*/
String[] value();
}
然后我们的实体类加上相应的注解
// Test实体加上注解后
public class Test {
// 表明 userName 字段的值来自于参数中的 user_name
@ValueFrom("user_name")
private String userName;
@ValueFrom("home_address")
private String address;
}
DataBinder
//
public class AliasDataBinder extends ExtendedServletRequestDataBinder {
public AliasDataBinder(Object target, String objectName) {
super(target, objectName);
}
/**
* 复写addBindValues方法
* @param mpvs 这里面存的就是请求参数的key-value对
* @param request 请求本身, 这里没有用到
*/
@Override
protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
super.addBindValues(mpvs, request);
// 处理要绑定参数的对象
Class<?> targetClass = getTarget().getClass();
// 获取对象的所有字段(拿到Test类的字段)
Field[] fields = targetClass.getDeclaredFields();
// 处理所有字段
for (Field field : fields) {
// 原始字段上的注解
ValueFrom valueFromAnnotation = field.getAnnotation(ValueFrom.class);
// 若参数中包含原始字段或者字段没有别名注解, 则跳过该字段
if (mpvs.contains(field.getName()) || valueFromAnnotation == null) {
continue;
}
// 参数中没有原始字段且字段上有别名注解, 则依次取别名列表中的别名, 在参数中最先找到的别名的值赋值给原始字段
for (String alias : valueFromAnnotation.value()) {
// 若参数中包含该别名, 则把别名的值赋值给原始字段
if (mpvs.contains(alias)) {
// 给原始字段赋值
mpvs.add(field.getName(), mpvs.getPropertyValue(alias).getValue());
// 跳出循环防止取其它别名
break;
}
}
}
}
}
AliasDataBinder
)要继承自ExtendedServletRequestDataBinder
addBindValues
方法, 该方法的第一个参数MutablePropertyValues
里面存的就是请求参数的key-value
对, 第二个参数是ServletRequest
, 这里没有用到getTarget()
是继承自DataBinder
的方法, 可以获取参数要绑定的实体类对象targetClass
的所有字段, 字段上有注解则处理, 没有则不处理ModelAttributeMethodProcessor
public class AliasModelAttributeMethodProcessor extends ServletModelAttributeMethodProcessor implements ApplicationContextAware {
private ApplicationContext applicationContext;
public AliasModelAttributeMethodProcessor(boolean annotationNotRequired) {
super(annotationNotRequired);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
AliasDataBinder aliasBinder = new AliasDataBinder(binder.getTarget(), binder.getObjectName());
RequestMappingHandlerAdapter requestMappingHandlerAdapter = applicationContext.getBean(RequestMappingHandlerAdapter.class);
requestMappingHandlerAdapter.getWebBindingInitializer().initBinder(aliasBinder);
aliasBinder.bind(request.getNativeRequest(ServletRequest.class));
}
}
ServletModelAttributeMethodProcessor
, 重写bindRequestParameters
方法, 这个方法就是绑定数据对象的时候调用的方法。ApplicationContextAware
是为了获取ApplicationContext
使用SpringMVC
时, 所有的请求都是最先经过DispatcherServlet
的, 然后由DispatcherServlet
选择合适的HandlerMapping
和HandlerAdapter
来处理请求, HandlerMapping
的作用就是找到请求所对应的方, 而HandlerAdapter
则来处理和请求相关的的各种事情。我们这里用的请求参数绑定也是通过HandlerAdapter
来做的, 父类ServletModelAttributeMethodProcessor
实际上实现了HandlerMethodArgumentResolver
接口。该接口有两个方法如下:
public interface HandlerMethodArgumentResolver {
/**
* 返回是否支持这种参数
*/
boolean supportsParameter(MethodParameter parameter);
/**
* 是具体处理参数的方法
*/
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}
之所以单独拿出来说, 是因为后面还会涉及到。ServletModelAttributeMethodProcessor
是用来处理复杂对象的(非基本类型), 比如我们定义的Test
。
我们定义好了属性处理器, 还要把它添加到Spring中才能生效
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Bean
protected AliasModelAttributeMethodProcessor processor() {
return new AliasModelAttributeMethodProcessor(true);
}
}
构造器中传了一个参数true
, 这是因为ModelAttributeMethodProcessor
是否支持某种类型的参数,是这样判断的
// ServletModelAttributeMethodProcessor的父类
public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
}
首先, 判断参数对象对象是否有ModelAttribute
注解, 有则处理; 如果没有,则判断annotationNotRequired
(注解 不是 必需的), 如果为true
(表示非必需)并且参数对象不是简单对象, 则处理。这里参数对象Test
是没有ModelAttribute
注解的, 所以我们就需要传参为true
, 表示不一定需要注解。
通过以上步骤, 则可以灵活控制参数的对应关系了。