Issue
I'm unable to successfully bind request parameters to a command object having a generic list property items
:
public class Holder<T> {
private List<T> items = new ArrayList<>();
public List<T> getItems() {
return items;
}
public void setItems(List<T> items) {
this.items = items;
}
}
Setting items via request parameters appears to work but the values remain Strings just waiting to trigger ClassCastExceptions
.
For example, the following code throws a ClassCastException
given the invocation
/test?items=1
and Controller
handler:
@GetMapping("/test")
public String test(Holder<Integer> holder) {
List<Integer> items = holder.getItems();
System.out.println(items.size()); // 1
System.out.println(items); // [1]
if (!items.isEmpty()) {
System.out.println(items.get(0).getClass()); // java.lang.ClassCastException: java.lang.String incompatible with java.lang.Integer
}
return "test";
}
How can I configure Spring MVC to convert the item parameter values to the correct (generic) type?
I'm using Spring Boot 2.3.2.
Solution
You can implement your own HandlerMethodArgumentResolver like this:
@Component
public class HolderMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return Holder.class.isAssignableFrom(methodParameter.getParameterType());
}
@Override
public Holder resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) {
Class<?> genericClass = (Class<?>) ((ParameterizedType) methodParameter.getGenericParameterType()).getActualTypeArguments()[0];
if (Integer.class.isAssignableFrom(genericClass)) {
String[] values = nativeWebRequest.getParameterValues("items");
List<Integer> convertedValues = Stream.of(values).map(Integer::valueOf).collect(Collectors.toList());
return new Holder<>(convertedValues);
}
if (String.class.isAssignableFrom(genericClass)) {
String[] values = nativeWebRequest.getParameterValues("items");
return new Holder<>(Arrays.asList(values));
}
throw new IllegalArgumentException("Generic type " + genericClass + " doesn't support");
}
}
And after that you can define in contoller different methods:
@GetMapping("/test")
public String test(Holder<Integer> holder) {
List<Integer> items = holder.getItems();
System.out.println(items.size());
System.out.println(items);
if (!items.isEmpty()) {
System.out.println(items.get(0).getClass());
}
return "test";
}
@GetMapping("/test2")
public String test2(Holder<String> holder) {
List<String> items = holder.getItems();
System.out.println(items.size());
System.out.println(items);
if (!items.isEmpty()) {
System.out.println(items.get(0).getClass());
}
return "test2";
}
Answered By - saver