Spring MVC Form: data binding of a list of abstract class

无人久伴 提交于 2019-12-10 19:04:59

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!