I am new to Spring and reading "Spring in Action" (which is a good book indeed). Here I encountered a problem in one of the example in this book that confused me a lot. In this example, there are two controllers correspond to two paths respectively. One is DesignTacoController and the other one is OrderController.
During my own trial on this example, I firstly copied OrderController which is a very simple class like below. This piece of code works perfectly when there is/isn't error input on web page.
@Slf4j @Controller @RequestMapping("/orders") public class OrderController { @GetMapping("/current") public String orderForm(Model model) { model.addAttribute("order", new Order()); return "orderForm"; } @PostMapping public String processOrder(@Valid Order order, Errors errors) { if (errors.hasErrors()) { return "orderForm"; } log.info("Order submitted: " + order); return "redirect:/"; } } And then I implemented DesignTacoController by myself according to OrderController above
@Slf4j @Controller @RequestMapping("/design") public class DesignTacoController { @GetMapping public String showDesignForm(Model model) { List<Ingredient> ingredients = Arrays.asList( // adding some ingredients ); Type[] types = Ingredient.Type.values(); for (Type type: types) { model.addAttribute( type.toString().toLowerCase(Locale.ROOT), filterByType(ingredients, type)); } model.addAttribute("design", new Taco()); return "design"; } @PostMapping public String processDesign(@Valid Taco design, Errors errors) { if (errors.hasErrors()) { return "design"; } log.info("Processing design: " + design); return "redirect:/orders/current"; } // more methods ignored } With this implementation, it works fine when there is no error inputs. However, if I input something against the validator, I will get a
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'design' available as request attribute By searching on stackoverflow (this question) and checking the original example code on github, I found this class should be implemented like this
@Slf4j @Controller @RequestMapping("/design") public class DesignTacoController { @ModelAttribute public void addIngredientsToModel(Model model) { List<Ingredient> ingredients = Arrays.asList( // adding some ingredients ); Type[] types = Ingredient.Type.values(); for (Type type : types) { model.addAttribute(type.toString().toLowerCase(), filterByType(ingredients, type)); } } @GetMapping public String showDesignForm(Model model) { model.addAttribute("design", new Taco()); return "design"; } @PostMapping public String processDesign(@Valid @ModelAttribute("design") Taco design, Errors errors, Model model) { if (errors.hasErrors()) { return "design"; } // Save the taco design... // We'll do this in chapter 3 log.info("Processing design: " + design); return "redirect:/orders/current"; } // more methods ignored } Here comes my questions,
- Why does OrderController work with no problem even without using @ModelAttribute annotation?
- Why do we need @ModelAttribute in DesignTacoController
- When will addIngredientsToModel be called?
- Can we merge addIngredientsToModel and showDesignForm?
I think I am lacking of some basic concepts of Spring. Hopefully somebody can help me out with this.
Thank you very much.