如果您在请求中使用camelCase参数,则将带有Spring MVC的URL请求参数映射到对象是相当简单的,但是当使用连字符分隔值时,如何将这些参数映射到对象?
参考示例:
控制器:
@RestController
public class MyController {
@RequestMapping(value = "/search", method = RequestMethod.GET)
public ResponseEntity<String> search(RequestParams requestParams) {
return new ResponseEntity<>("my-val-1: " + requestParams.getMyVal1() + " my-val-2: " + requestParams.getMyVal2(), HttpStatus.OK);
}
}
保存参数的对象:
public class RequestParams {
private String myVal1;
private String myVal2;
public RequestParams() {}
public String getMyVal1() {
return myVal1;
}
public void setMyVal1(String myVal1) {
this.myVal1 = myVal1;
}
public String getMyVal2() {
return myVal2;
}
public void setMyVal2(String myVal2) {
this.myVal2 = myVal2;
}
}
这样的请求可以正常工作:
GET http://localhost:8080/search?myVal1=foo&myVal2=bar
但是,我想要的是用连字符映射到对象的请求,如下所示:
GET http://localhost:8080/search?my-val-1=foo&my-val-2=bar
我需要在Spring中配置什么来将带连字符的url请求参数映射到对象中的字段?请记住,我们可能有许多参数,所以使用每个字段的@RequestParam注释都不理想。
答案 0 :(得分:5)
我扩展了ServletRequestDataBinder和ServletModelAttributeMethodProcessor以解决问题。
请考虑您的域对象可能已经使用@JsonProperty或@XmlElement进行注释以进行序列化。这个例子假设是这种情况。但您也可以为此目的创建自己的自定义注释,例如@MyParamMapping。
带注释的域类的示例是:
public class RequestParams {
@XmlElement(name = "my-val-1" )
@JsonProperty(value = "my-val-1")
private String myVal1;
@XmlElement(name = "my-val-2")
@JsonProperty(value = "my-val-2")
private String myVal2;
public RequestParams() {
}
public String getMyVal1() {
return myVal1;
}
public void setMyVal1(String myVal1) {
this.myVal1 = myVal1;
}
public String getMyVal2() {
return myVal2;
}
public void setMyVal2(String myVal2) {
this.myVal2 = myVal2;
}
}
您需要一个SerletModelAttributeMethodProcessor来分析目标类,生成映射,调用您的ServletRequestDataBinder。
public class KebabCaseProcessor extends ServletModelAttributeMethodProcessor {
public KebabCaseProcessor(boolean annotationNotRequired) {
super(annotationNotRequired);
}
@Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
private final Map<Class<?>, Map<String, String>> replaceMap = new ConcurrentHashMap<Class<?>, Map<String, String>>();
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest nativeWebRequest) {
Object target = binder.getTarget();
Class<?> targetClass = target.getClass();
if (!replaceMap.containsKey(targetClass)) {
Map<String, String> mapping = analyzeClass(targetClass);
replaceMap.put(targetClass, mapping);
}
Map<String, String> mapping = replaceMap.get(targetClass);
ServletRequestDataBinder kebabCaseDataBinder = new KebabCaseRequestDataBinder(target, binder.getObjectName(), mapping);
requestMappingHandlerAdapter.getWebBindingInitializer().initBinder(kebabCaseDataBinder, nativeWebRequest);
super.bindRequestParameters(kebabCaseDataBinder, nativeWebRequest);
}
private static Map<String, String> analyzeClass(Class<?> targetClass) {
Field[] fields = targetClass.getDeclaredFields();
Map<String, String> renameMap = new HashMap<String, String>();
for (Field field : fields) {
XmlElement xmlElementAnnotation = field.getAnnotation(XmlElement.class);
JsonProperty jsonPropertyAnnotation = field.getAnnotation(JsonProperty.class);
if (xmlElementAnnotation != null && !xmlElementAnnotation.name().isEmpty()) {
renameMap.put(xmlElementAnnotation.name(), field.getName());
} else if (jsonPropertyAnnotation != null && !jsonPropertyAnnotation.value().isEmpty()) {
renameMap.put(jsonPropertyAnnotation.value(), field.getName());
}
}
if (renameMap.isEmpty())
return Collections.emptyMap();
return renameMap;
}
}
此KebabCaseProcessor将使用反射来获取请求对象的映射列表。然后它将调用KebabCaseDataBinder - 传入映射。
@Configuration
public class KebabCaseRequestDataBinder extends ExtendedServletRequestDataBinder {
private final Map<String, String> renameMapping;
public KebabCaseRequestDataBinder(Object target, String objectName, Map<String, String> mapping) {
super(target, objectName);
this.renameMapping = mapping;
}
protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
super.addBindValues(mpvs, request);
for (Map.Entry<String, String> entry : renameMapping.entrySet()) {
String from = entry.getKey();
String to = entry.getValue();
if (mpvs.contains(from)) {
mpvs.add(to, mpvs.getPropertyValue(from).getValue());
}
}
}
}
现在剩下的就是将此行为添加到您的配置中。以下配置将覆盖@EnableWebMVC提供的默认配置,并将此行为添加到请求处理中。
@Configuration
public static class WebContextConfiguration extends WebMvcConfigurationSupport {
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(kebabCaseProcessor());
}
@Bean
protected KebabCaseProcessor kebabCaseProcessor() {
return new KebabCaseProcessor(true);
}
}
应该给@Jkee。这个解决方案是他在这里发布的一个例子的衍生物:How to customize parameter names when binding spring mvc command objects。
答案 1 :(得分:1)
我能想到绕过连字符的一种方法是使用HttpServletRequestWrapper
类来包装原始请求。
解析此类中的所有请求参数,并将所有带连字符的参数转换为camelcase。在此之后,spring将能够自动将这些参数映射到您的POJO类。
public class CustomRequestWrapper extends HttpServletRequestWrapper {
private Map<String, String> camelCasedParams = new Hashmap();
public CustomRequestWrapper(HttpServletRequest req){
//Get all params from request.
//Transform each param name from hyphenated to camel case
//Put them in camelCasedParams;
}
public String getParameter(String name){
return camelCasedParams.get(name);
}
//Similarly, override other methods related to request parameters
}
从J2EE过滤器中注入此请求包装器。您可以参考以下链接获取有关使用过滤器注入请求包装器的教程。
http://www.programcreek.com/java-api-examples/javax.servlet.http.HttpServletRequestWrapper
更新您的网络xml以包含过滤器及其过滤器映射。