3

I am upgrading from Spring 2.5 to Spring 3.2. I have a MVC Controller that previously extended CancellableFormController. It declared,separately, initBinder() and onBind() methods. I have refactored the Controller to use @Controller annotation, and switched the overridden initBinder() method to use Spring MVC 3 annotation @initBinder.

My specific question is, in Spring MVC 3, upgrading from Spring 2.5, how to refactor the overridden onBind() method to use an annotation equivalent? The signature of the existing method is:

@Override
protected void onBind(HttpServletRequest request, Object command, BindException errors)  throws Exception {
    MyCommand cmd = (MyCommand) command;
    ....
}

I thought about using @initBinder() and put the code previously in onBind() inside this annotated method. But my confusion here is:

  1. Doing this, would the code be called at the same time in the overall framework process as before?
  2. How to get a handle on the Command object from @initBinder annotated method.

Can I just declare it as another parameter in the signature of the method and Spring MVC framework will ensure I get a copy? It seems that in the old onBind() method the Command object has already been created (by formBackingObject method). Can I safely assume that this is also the case when using @initBinder method?

Thank you anyone for some insights. I am trying to get up to speed with the Spring MVC process flow!

My existing @initBinder method signature is something like:

@InitBinder
protected void initBinder(WebDataBinder binder) {
    // register custom editor here.. etc
}

I am hoping I can do something like:

@InitBinder
protected void initBinder(WebDataBinder binder, MyCommand cmd) {
   // register custom editor here.. etc
}

Is this the standard best practice approach for upgrading cancellableformcontroller onBind() method using annotations?

Attempt based on answer marked correct, still not working:

@InitBinder("myCommand")
protected void onBind(WebDataBinder binder) throws Exception {
    MyCommand cmd = (MyCommand) binder.getTarget();
    .... // do whatever was required here...
}

Work around

Please see my comments below to zeroflag. Create a private method with same logic as contained in onBind(), and then after validation of command object in onSubmit() annotated method (POST / GET) make a callout to the now defunct onBind() method passing your Command object in as a parameter. Something like the following:

@RequestMapping(method=RequestMethod.POST) 
public ModelAndView onSubmit(@ModelAttribute("myCommand") MyCommand cmd, BindingResult result, <any other params> {
    
        new MyCommandValidator().validate(cmd, result);
        if (result.hasErrors()) {
            return new ModelAndView("context");
        }
        onBind(cmd, <any other params>);

        ... // do business stuff

}

It appears to be an ugly workaround that was ok for my particular case.

Community
  • 1
  • 1
arcseldon
  • 35,523
  • 17
  • 121
  • 125
  • Before I answer: Why do you want to acccess the command object? I'm pretty sure there is a more "automagical" solution in current Spring MVC :) – a better oliver Apr 30 '13 at 07:03
  • Thanks for taking the time and effort to ask. Basically, this is inherited code (legacy app around 5 yrs old) so the exact context is lost. From what I read, the original purpose was to inspect the Command object, and read several attributes, then finally update some information in the command object. Now, why this had to happen in onBind() as opposed to some other hook method in the process is not abundantly clear. Let's just assume the user wished to ensure that the Command Object was fully updated at bind time ;) Do you know of an equivalent mechanism in Spring MVC 3 with annotations? – arcseldon Apr 30 '13 at 09:06
  • @zeroflagL - please see comment above. – arcseldon Apr 30 '13 at 09:07

1 Answers1

1

You can access the command object with binder.getTarget(). @InitBinder is called for every model parameter of the controller method. Let's assume the following code

@RequestMapping("/somePath")
public String myMethod(@ModelAttribute("user") User user,
                       @ModelAttribute("info") Info info) {

Then your initBinder method is called at least twice: For the Userobject and for the Infoobject. To have it called only for a specific object add the model name:

@InitBinder("user")

That way it will only be called for the Userobject. Be aware, though, that the method might still be called more than once, the first time even with the target object being null.

If you want to ensure that some fields are not set automatically, then you can use setDisallowedFields() in your initBindermethod.

If you want to do some validation then let JSR 303 (Bean Validation) do that job.

Depending on what you want to do an @ModelAttribute method in your controller could be a solution:

@ModelAttribute("user")
public User createUser(HttpServletRequest request /*or whatever*/) {
  User user = new User();
  //check some parameters and set some attributes
  return user;
}

Creates a user object before binding happens.

One last solution can be a message or type converter for your command object, that creates an object from the request. That's actually similar to the example above, but independent of the controller and there is no binding for an object created with a converter.

a better oliver
  • 26,330
  • 2
  • 58
  • 66
  • believe the first part of your answer is exactly what I needed to know. Please allow me a little time to update my code and test. All going well, I shall mark this answer as the correct one. Appreciate the help!! – arcseldon Apr 30 '13 at 09:50
  • sorry to take so long to update my code and check your solution. Yes, it is the right answer and solved my issue. Shall update my question with solution I used based on your solution. Thank you! – arcseldon May 06 '13 at 11:06
  • ok, finally, i discovered this does not work as expected. the command object being returned from getTarget() is unpopulated of values. Whereas with onBind() it was passing the form populated values (as attributes inside command object) and getting called prior to onSubmit(). – arcseldon May 07 '13 at 16:14
  • @user36123 it's called `initBinder` for a reason ;) That's why I've provided some more options. Could you post the relevant code of your current `onBind()` method? Then we could figure out the best migration path for your application. – a better oliver May 07 '13 at 16:42
  • zeroflagL - Thank you again for offering further insights. I ultimately achieved what I needed simply by making a private / protected method in the controller that contained the same logic as onBind before, and explicitly calling it from with the annotated (POST) onSubmit() method, passing the command object as a param. It is a cop out, and perhaps not using the Spring framework template methods to get the precise timing as before (although it "appeared" onBind was getting called just prior to onSubmit in old controller) but it satisfied my specific requirements on this occasion. Thanks again. – arcseldon May 09 '13 at 00:25