在Spring Web MVC中使用破折号将URL参数映射到对象

时间:2015-11-25 18:44:48

标签: java spring spring-mvc

如果您在请求中使用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注释都不理想。

2 个答案:

答案 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类来包装原始请求。

  1. 解析此类中的所有请求参数,并将所有带连字符的参数转换为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
    }
    
  2. 从J2EE过滤器中注入此请求包装器。您可以参考以下链接获取有关使用过滤器注入请求包装器的教程。

    http://www.programcreek.com/java-api-examples/javax.servlet.http.HttpServletRequestWrapper

  3. 更新您的网络xml以包含过滤器及其过滤器映射。