绑定SpringMvc GET请求对象时自定义参数名
2018-09-12 本文已影响4人
tenlee
背景
定义一个参数对象
public class Job {
private String jobType;
private String location;
}
在Spring MVC
的GET
请求使用它
@GetMapping("/foo")
public Job doSomethingWithJob(Job job) {
...
}
请求http://example.com/foo?jobType=permanent&location=Stockholm
会得到正确显示,
请求http://example.com/foo?job_type=permanent&location=Stockholm
不会得到正确显示
这时候就需要自定义GET请求的字段名了,Spring对这样的支持可能不太完美。
PS:如果有这样的需求,还是建议使用POST JSON这种方式吧。
解决方案
参考stackoverflow,但是该解决方法不太完美,不支持类继承情况下的别名映射。
本人修改后完整代码如下:
自定义一个注解
/**
* Overrides parameter name
* @author jkee
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ParamName {
/**
* The name of the request parameter to bind to.
*/
String value();
}
使用ServletRequestDataBinder
给真实的Filed赋值
/**
* 将{@link ParamName}注释的field加入MutablePropertyValues,是spring能够注入该值
* @author tenlee
*/
public class ParamNameDataBinder extends ExtendedServletRequestDataBinder {
private final Map<String, String> renameMapping;
public ParamNameDataBinder(Object target, String objectName, Map<String, String> renameMapping) {
super(target, objectName);
this.renameMapping = renameMapping;
}
@Override
protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
super.addBindValues(mpvs, request);
for (Map.Entry<String, String> entry : renameMapping.entrySet()) {
String from = entry.getKey(); // ParamName.value的值,即原请求参数的key,可能和field name不同
String to = entry.getValue(); // field name
if (mpvs.contains(from)) {
mpvs.add(to, mpvs.getPropertyValue(from).getValue()); // 设置field name 的值,使spring能注入
}
}
}
}
处理Field和别名的映射关系
/**
* Method processor supports {@link ParamName} parameters renaming
*
* @author tenlee
*/
public class RenamingProcessor extends ServletModelAttributeMethodProcessor {
@Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
/**
* A map caching annotation definitions of command objects (@ParamName-to-fieldname mappings)
*/
private final Map<Class<?>, Map<String, String>> replaceMap = new ConcurrentHashMap<>();
public RenamingProcessor(boolean annotationNotRequired) {
super(annotationNotRequired);
}
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest nativeWebRequest) {
Object target = binder.getTarget();
Map<String, String> mapping = getFieldMapping(target.getClass());
ParamNameDataBinder paramNameDataBinder = new ParamNameDataBinder(target, binder.getObjectName(), mapping);
requestMappingHandlerAdapter.getWebBindingInitializer().initBinder(paramNameDataBinder, nativeWebRequest);
super.bindRequestParameters(paramNameDataBinder, nativeWebRequest);
}
private Map<String, String> getFieldMapping(Class<?> targetClass) {
if (targetClass == Object.class) {
return Collections.emptyMap();
}
if (replaceMap.containsKey(targetClass)) {
return replaceMap.get(targetClass);
}
Map<String, String> renameMap = new HashMap<>();
Field[] fields = targetClass.getDeclaredFields();
for (Field field : fields) {
ParamName paramNameAnnotation = field.getAnnotation(ParamName.class);
if (paramNameAnnotation != null && !paramNameAnnotation.value().isEmpty()) {
renameMap.put(paramNameAnnotation.value(), field.getName());
}
}
// 递归获取全部Field
renameMap.putAll(getFieldMapping(targetClass.getSuperclass()));
if (renameMap.isEmpty()) {
renameMap = Collections.emptyMap();
}
replaceMap.put(targetClass, renameMap);
return renameMap;
}
}
配置ServletModelAttributeMethodProcessor
生效
/**
* @author tenlee
*/
@Configuration
public class WebContextConfiguration extends WebMvcConfigurerAdapter {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(renamingProcessor());
}
@Bean
protected RenamingProcessor renamingProcessor() {
return new RenamingProcessor(true);
}
}
参考: