Story
When we have a component that is used in multiple views - such as a navigation menu with a menu service that retrieves the menu items from a repository, we need to add this data to the model. However, the navigation menu always appear in many views, it is unwise to write the code to call the menu service and bind to the model in all related controllers.
Solution
Solution 1
One solution is to create a abstract controller that implements this logic and ask all other controllers in whose views the navigation menu appears to extend this abstract controller. The downside of this approach is that one class can only extend only one super class without using the Java 8 default Interface implementation. When there are different model attributes that needs to be added to different controllers, this method will create chaos in managing these models.
Solution 2
Another solution is to apply Spring AOP. The aspect that gets applied is the model attributes bind, and the cross-cutting points are the controllers that need this model. To use this approach, AspectJ syntax knowledge is needed.
Solution 3
The simples solution is to use the @ControllerAdvice
annotation which could be applied to all controllers or selected controllers by annotation
or basePackages
or basePackageClasses
.
For instance, to bind menu item model attribute to all controller classes, we could just write this:
@ControllerAdvice public class MenuItemController { private MenuItemService menuItemService; public MenuItemController(final MenuItemService menuItemService) { this.menuItemService = menuItemService; } @ModelAttribute("menuItems") public List<MenuItem> getMenuItems () { return menuItemService.findAll(); } }
Lesson Learned
@ControllerAdvice
is a specialized@Component
that could be used to do not just exception handling, but also model attributes binding in a cross-cutting way.