3

Using a Spring MVC @Controller, how do I have a @RequestMapping endpoint have a @ModelAttribute declared as an interface?

I want to have three different forms being posted to the mapping that the underlying classes are all of an interface type.

So for example, I can have three different form objects with the action to the following:

@RequestMapping(path="/doSomething", method=RequestMethod.POST)
public String doSomething(ObjectInterface formInfo) {
   ...
}

(Where ObjectInterface is an interface, not a concrete class.)

FiguringThisOut
  • 810
  • 2
  • 9
  • 18
  • I can write code myself to direct Spring to the correct concrete bean class to bind the request to that it can instantiate for the request if I can inspect the incoming data, as that class implements the interface. Then Spring should simply be able to pass that into the method. I am wondering if it is possible to implement this some how. It should easily be possible if Spring allows for such customization. – FiguringThisOut Feb 02 '16 at 22:25
  • I think this is a good idea in terms of general design but not for Spring and model attributes. Spring maps the input values on the form to the fields of the entity. Plus, I think best practice is to have a single controller for each "section" of the app. It seems like you're binding views from different domains to a common controller; maybe I'm misunderstanding. – ChiefTwoPencils Feb 02 '16 at 22:35
  • I think writing and registering a custom HandlerMethodArgumentResolver might do the trick. I will update once I have something working, if that is the solution. – FiguringThisOut Feb 02 '16 at 23:56

2 Answers2

2

It can be done using request level model attributes as follows:

Suppose There is ObjectInterace is an interface with three implementation classes as ObjectA, ObjectB and ObjectC. then Controller method declaration is:

@RequestMapping(path="/doSomething", method=RequestMethod.POST)
public String doSomething(@ModelAttribute("object") ObjectInterface formInfo) {
   ...
}

Add Method to populate modelattribute in the controller class:

    @ModelAttribute("object")
    public ObjectInterface getModelObject(HttpServletRequest request) {
     ObjectInterface object = null;
     String type = request.getParameter("type");
      if (StringUtils.equals("A", type)) {
        object= new objectA();
      } else if (StringUtils.equals("B", type)){
        object= new objectB();
      }else if (StringUtils.equals("C", type)){
        object= new objectC();
       }else{
          //object=any default object.
       }
     return object ;
   }

the value returned by getModelObject is added to the Model and it will be populated with the values from the view to the controller method.

Before invoking the handler method, Spring invokes all the methods that have @ModelAttribute annotation. It adds the data returned by these methods to a temporary Map object. The data from this Map would be added to the final Model after the execution of the handler method.

Deo Priya
  • 91
  • 1
  • 1
  • 6
  • You are some kind of genius. I won't tell you how many days it took before I finally found this post of yours and solved my problem. – mwarren Apr 20 '23 at 15:37
0

Figured it out. It is to write and register a custom HandlerMethodArgumentResolver. Below is the core code. You just need to figure out which concrete bean class to pass into the webDataBinderFactory. Your controller can then be written to accept an interface and you will be provided the concrete implementing bean behind the interface.

public class MessageResolverTest implements HandlerMethodArgumentResolver {

    public boolean supportsParameter(MethodParameter methodParameter) {
        return methodParameter.getParameterType().equals(<Interface>.class);
    }

    public Object resolveArgument(MethodParameter methodParameter,
                                  ModelAndViewContainer modelAndViewContainer,
                                  NativeWebRequest nativeWebRequest,
                                  WebDataBinderFactory webDataBinderFactory) throws Exception {

        String name = ModelFactory.getNameForParameter(methodParameter);
        WebDataBinder webDataBinder = webDataBinderFactory.createBinder(nativeWebRequest, new <ConcreteBean>(), name);
        MutablePropertyValues mutablePropertyValues = new MutablePropertyValues(nativeWebRequest.getParameterMap());
        webDataBinder.bind(mutablePropertyValues);

        return webDataBinder.getBindingResult().getTarget();
    }
}
FiguringThisOut
  • 810
  • 2
  • 9
  • 18