0

This is how it all looks now:

@SessionAttributes("shoppingCart")
public class ItemController {

    @ModelAttribute
    public ShoppingCart createShoppingCart() {
        return new ShoppingCart();
    }

    @RequestMapping(value=RequestMappings.ADD_TO_CART + RequestMappings.PARAM_ITEM_ID, method=RequestMethod.GET)
    public String addToCart(@PathVariable("itemId") Item item, @ModelAttribute ShoppingCart shoppingCart) {

        if(item != null) {
            shoppingCartService.addItem(shoppingCart, item);
        }

        return ViewNamesHolder.SHOPPING_CART;
    }
}

When the addToCart method is called first time, the shoppingCart object will be initialized by the createShoppingCart method. After the addToCart method runs, the initialized object will be added to the session and it will be used from the session for the later use. That means the createShoppingCart methode is called just once (as long as it does not removed from the session).

Why does Spring eliminate the need for the ModelAttribute annotated initializer method, by simply creating this object whenever is needed? Then it would all look simpler like this:

@SessionAttributes("shoppingCart")
public class ItemController {

    @RequestMapping(value=RequestMappings.ADD_TO_CART + RequestMappings.PARAM_ITEM_ID, method=RequestMethod.GET)
    public String addToCart(@PathVariable("itemId") Item item, @ModelAttribute ShoppingCart shoppingCart) {

        if(item != null) {
            shoppingCartService.addItem(shoppingCart, item);
        }

        return ViewNamesHolder.SHOPPING_CART;
    }
}

Whenever the shoppingCart object will not be found in the session, it would be initialized by its default constructor.. What do you think the reason is for that decision?

akcasoy
  • 6,497
  • 13
  • 56
  • 100

1 Answers1

1

I can't speak directly for the Spring team, but your suggestion would limit the desired ModelAttribute to a newly created instance on each request (prior to being stored in the session,) but what if you wanted to start with a fully populated object, say, fetched from a datastore? Your method offers no way to do that. This, however, works well:

@ModelAttribute
public ShoppingCart createShoppingCart() {
    ...
    return someShoppingCartRepo.find(...);
}

This, of course, is just one possible scenario where the usefulness of a separate method should be evident.

EDIT AFTER COMMENTS

You could easily create your own HandlerMethodArgumentResolver that would give you a new instance of your object if none existed, but it might be overkill considering how easy it is to use your createShoppingCart() method. If you are using xml configs, it would be something like this:

<mvc:annotation-driven ...>
    <mvc:argument-resolvers>
        <bean class="yourpackage.YourCustomArgumentResolver" />
    </mvc:argument-resolvers>
</mvc:annotation-driven>

You could extend any number of existing HandlerMethodArgumentResolver base classes, or you could implement the interface directly yourself, but most likely you would use something like this:

public class YourCustomArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
    // Implement/override methods for creating your model object when encountered as a method argument
}

To identify your argument, you could create a custom annotation:

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface YourAutoCreateModelAttribute {

    String value() default "";

    boolean required() default true;

    String defaultValue() default ValueConstants.DEFAULT_NONE;
}

Then annotate your method like this to kick off your custom resolver:

@RequestMapping(...)
public String doStuff(@YourAutoCreateModelAttribute ShoppingCart shoppingCart, ...) {
    // Now your shoppingCart will be created auto-magically if it does not exist (as long as you wrote your resolver correctly, of course.
}
MattSenter
  • 3,060
  • 18
  • 18
  • I did not mean exactly that. Of course there are many scenarios where we need a separate method for initialisation. Your code is a perfect example of one. But in case we don't want to have a separate method, Spring could have supported that too by not throwing an HttpSessionRequiredException, but by simply initialising the object with its default constructor and putting into the session.. – akcasoy Nov 10 '13 at 17:23
  • But what if your use case required a way to throw an exception if the model did not yet exist? For example, particularly with model attributes stored in the session, what if you had a few controllers that were part of a logical flow, and they all used the same model attribute, but the second and third controllers needed the model attribute to already exist? If Spring automatically created model attribute in some fashion, you would not be able to handle the HttpSessionRequiredException if the second controller was visited before the model was stored in the session with the first controller. – MattSenter Nov 10 '13 at 18:23
  • You are right. It is just looking awkward to me having almost useless 3 lines @ModelAttribute public ShoppingCart createShoppingCart() { return new ShoppingCart();} in nearly all controllers.. I would be happier having another shortcut for this initialization.. Sth like this: "@SessionAttributes("shoppingCart", new ShoppingCart())".. – akcasoy Nov 11 '13 at 14:18
  • Updated my answer to get you started on a custom solution if you'd like. – MattSenter Nov 11 '13 at 15:16
  • 1
    I now had the chance to inspect your approach. As you already wrote, it is an overkill for this situation but the idea behind is super. I did not aware of this kind of possibilities of spring. Thank you! – akcasoy Nov 12 '13 at 20:13