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

And*_*rew 4 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);
    }

}
Run Code Online (Sandbox Code Playgroud)

保持参数的对象:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

像这样的请求工作正常:

GET http://localhost:8080/search?myVal1=foo&myVal2=bar
Run Code Online (Sandbox Code Playgroud)

但是,我想要的是用连字符映射到对象的请求,如下所示:

GET http://localhost:8080/search?my-val-1=foo&my-val-2=bar
Run Code Online (Sandbox Code Playgroud)

我需要在Spring中配置什么来将带连字符的url请求参数映射到对象中的字段?请记住,我们可能有许多参数,因此对每个字段使用@RequestParam注释并不理想.

小智 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

您将需要一个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;
    }
}
Run Code Online (Sandbox Code Playgroud)

此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());
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在剩下的就是将此行为添加到您的配置中.以下配置将覆盖@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);
    }
} 
Run Code Online (Sandbox Code Playgroud)

应该给@Jkee.这个解决方案是他在这里发布的一个例子的衍生物: 如何在绑定spring mvc命令对象时自定义参数名称.