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?
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.
}