问题
I've inherited some code that I need add functionality to and it involves a program built on Spring and Spring Form with a list and binding abstract elements
So we have an abstract class like the following
public abstract class A
{
private int id;
private String name;
private int type;
....getters and setters below
}
And a few implementations such as
public class AImplOne extends A
{
private String text;
...getters, setters etc
}
public class AImplTwo extends A
{
private int mediaID;
...getters, setters etc
}
So the AImpl classes implement A, but have an extra piece of information each depending on the implementation type.
Then we have a class B that has list of A objects which contains a list of the A implementations (can be mixed list of different implementation types)
public class B
{
private List<A> aList;
public B() { aList = new ArrayList<A>(); }
..getters and setters, etc
}
Now we have a form built in Spring as follows that is used to create B objects including the implementations of A that go into aList. The following JSP code is inside of a loop that has a loopStatus value ${index}. This is also loaded via a Controller, that has class A
The following JSP code is in a loop that creates the inputs for the form:
<form:form method="POST" modelAttribute="B">
</form:form>
The first iteration will set data for a AImplOne object
<form:input type="hidden" path="aList[${index}].id" value="0" />
<form:input type="hidden" path="aList[${index}].type" value="Type1" />
<form:input type="hidden" path="aList[${index}].name" value="X" />
<form:input type="hidden" path="aList[${index}].text" value="aaaa" />
And then in the next iteration we set the data for a class AImplTwo type
<form:input type="hidden" path="aList[${index}].id" value="0" />
<form:input type="hidden" path="aList[${index}].type" value="Type2" />
<form:input type="hidden" path="aList[${index}].name" value="X" />
<form:input type="hidden" path="aList[${index}].mediaID" value="12" />
So the problem I've run into is that loading this JSP fails and throws an error
Caused by: org.springframework.beans.InvalidPropertyException: Invalid property 'aList[0]' of bean class [B]: Illegal attempt to get property 'aList' threw exception; nested exception is org.springframework.beans.NullValueInNestedPathException: Invalid property 'aList' of bean class [B]: Could not instantiate property type [A] to auto-grow nested property path: java.lang.InstantiationException
This is completely understandable as aList has elements of abstract class A so it can't instantiate that class A. However, based on the inputs I can determine that one is AImplOne and the other is AImplTwo
What I'm trying to be able to do, is essentially implement functionality that can override Springs default form binding for this to be able to correctly initialize the correct implementation and be able to dynamically generate this aList to have [AImplOne, AImpleTwo] as it's content.
Is there a resolver class or something along those lines that I can implement to provide this custom functionality for the program (read the data and instantiate the correct object)
回答1:
What I'm trying to be able to do, is essentially implement functionality that can override Springs default form binding for this to be able to correctly initialize the correct implementation and be able to dynamically generate this aList to have [AImplOne, AImpleTwo] as it's content.
You can do this using custom init binder method in your controller class:
// Additionally using name of object used with @ModelAttribute
// as "value" parameter of this annotation is not working in some cases
@InitBinder
public void initBinder(
WebDataBinder webDataBinder, HttpServletRequest httpServletRequest) {
// You only want to init this when form is submitted
if (!"POST".equalsIgnoreCase(httpServletRequest.getMethod()) {
return;
}
// Filter out all request when we have nothing to do
Object nonCastedTarget = webDataBinder.getTarget();
if (nonCastedTarget == null || !(nonCastedTarget instanceof B)) {
return;
}
// TODO: Better cache this in static final field instead
Pattern pattern = Pattern.compile("aList\\[(\\d+)]\\.type");
Map<Integer, String> types = new HashMap<>();
Enumeration<String> parameterNames = httpServletRequest.getParameterNames();
while (parameterNames.hasMoreElements()) {
String element = parameterNames.nextElement();
Matcher matcher = pattern.matcher(element);
if (!matcher.matches()) {
continue;
}
types.put(
Integer.parseInt(matcher.group(1)),
httpServletRequest.getParameter(element)
);
}
B target = (B) nonCastedTarget;
List<A> aList = target.getAList();
if (aList == null) {
target.setAList(new ArrayList<>());
}
types.keySet().stream().sorted().forEach(key -> {
switch (types.get(key)) {
case "Type1":
target.getAList().add(new AImplOne());
break;
case "Type2":
target.getAList().add(new AImplTwo());
break;
default:
throw new IllegalStateException("Unknown type: " + key);
}
});
}
The code above is just a simple working version, e.g. almost no error handling is implemented, no extracted constants - so you must improve it to make it more production-ready.
来源:https://stackoverflow.com/questions/37847549/spring-mvc-form-data-binding-of-a-list-of-abstract-class