57

Every single view in my Spring 3 app has a set of attributes they can rely on. So the first line of every controller is something like:

ControllerHelper.addDefaultModel(model, personManager, request);

In there I'll add

  • user object and full name retrieved from the database if person is logged in
  • set of variables which are typically set once (e.g. imagesHost)
  • set of languages a visitor can switch to
  • current language
  • some statistics (e.g. total # of people in our system)

This all allows each view to display the logged in user's name, easily reference an image location, a list of languages and some overall stats about the site.

So the question is, is the controller model object the best place to store all the data or is there a more convenient place which makes it just as easy for views to access this info?

And secondly, I'd REALLY love not to have to have the ControllerHelper line above as the first line in every controller. It's actually not always the first line, sometimes I first check if I need to redirect in that controller, because I don't want to waste resources filling the model for no reason. Maybe a filter or annotation or some Spring callback mechanism could make sure the ControllerHelper code is called after the controller is finished but right before the view is rendered, skipping this if a redirect was returned?

tereško
  • 58,060
  • 25
  • 98
  • 150
at.
  • 50,922
  • 104
  • 292
  • 461
  • 4
    Can't you just put this stuff in the session? – blank Sep 09 '11 at 11:17
  • so the user object and full name maybe I can put in the session, the rest of the stuff doesn't make any sense to put in the session. I keep the sessions in the database for easier clustering, so I try to keep it to a minimum. If I stored the user object and full name in the session, that means I'd be making database calls for every request. Which I'm doing anyway by retrieving those objects from the database on every request, but at least retrieving the objects allows for much easier hibernate caching. – at. Sep 09 '11 at 15:36
  • I have the same problem to resolve. @at. how did you resolve? – Stefano Sambruna Aug 31 '19 at 13:35

6 Answers6

59

You could write an org.springframework.web.servlet.HandlerInterceptor. (or its convenience subclass HandlerInterceptorAdapter)

@See: Spring Reference chapter: 15.4.1 Intercepting requests - the HandlerInterceptor interface

It has the method:

void postHandle(HttpServletRequest request,
                HttpServletResponse response,
                Object handler,
                ModelAndView modelAndView) throws Exception;

This method is invoked after the controller is done and before the view is rendered. So you can use it, to add some properties to the ModelMap

An example:

/**
 * Add the current version under name {@link #VERSION_MODEL_ATTRIBUTE_NAME}
 * to each model. 
 * @author Ralph
 */
public class VersionAddingHandlerInterceptor extends HandlerInterceptorAdapter {

    /**
     * The name under which the version is added to the model map.
     */
    public static final String VERSION_MODEL_ATTRIBUTE_NAME =
                "VersionAddingHandlerInterceptor_version";

    /**        
     *  it is my personal implmentation 
     *  I wanted to demonstrate something usefull
     */
    private VersionService versionService;

    public VersionAddingHandlerInterceptor(final VersionService versionService) {
        this.versionService = versionService;
    }

    @Override
    public void postHandle(final HttpServletRequest request,
            final HttpServletResponse response, final Object handler,
            final ModelAndView modelAndView) throws Exception {

        if (modelAndView != null) {
            modelAndView.getModelMap().
                  addAttribute(VERSION_MODEL_ATTRIBUTE_NAME,
                               versionService.getVersion());
        }
    }
}

webmvc-config.xml

<mvc:interceptors>
    <bean class="demo.VersionAddingHandlerInterceptor" autowire="constructor" />
</mvc:interceptors>
Ralph
  • 118,862
  • 56
  • 287
  • 383
  • 1
    As specified in http://docs.spring.io/spring/docs/3.0.x/javadoc-api/org/springframework/web/servlet/ModelAndView.html#getModelMap() `getModelMap()` never return `null` – igo Dec 24 '13 at 12:52
37

You can also use @ModelAttribute on methods e.g.

@ModelAttribute("version")
public String getVersion() {
   return versionService.getVersion();
}

This will add it for all request mappings in a controller. If you put this in a super class then it could be available to all controllers that extend it.

blank
  • 17,852
  • 20
  • 105
  • 159
  • 1
    Can you provide and example of putting it in a super class and how the subclass uses it? – trusktr Feb 28 '14 at 04:15
  • 1
    @trusktr I created a `RootController` class and made fields that were needed by subclasses `protected`...this approach worked easily and I was able to consolidate functionality that was needed by each controller into this `RootController` so that I didn't have to keep repeating the same code in subclasses ("DRY") – Zack Macomber Mar 09 '16 at 21:24
15

You could use a Controller Class annotated with @ControllerAdvice

"@ControllerAdvice was introduced in 3.2 for @ExceptionHandler, @ModelAttribute, and @InitBinder methods shared across all or a subset of controllers."

for some info about it have a look at this part of the video recorded during SpringOne2GX 2014 http://www.youtube.com/watch?v=yxKJsgNYDQI&t=6m33s

mickthompson
  • 5,442
  • 11
  • 47
  • 59
  • 2
    The `@ControllerAdvice` is a great alternative – Thiago Pereira Aug 05 '16 at 18:38
  • 2
    This should be the best answer, if you need the same model attributes for multiple controllers than the controller advice annotation is what you need. [spring docs](http://docs.spring.io/spring-framework/docs/4.1.6.RELEASE/spring-framework-reference/html/mvc.html#mvc-ann-controller-advice) – Grim Mar 03 '17 at 23:15
  • 1
    Great answer! Just a small remark: The class annotated with @ControllerAdvice can by any class and does not need to be a controller class. I used it on a service class. – derwiwie Jul 29 '17 at 11:59
7

like @blank answer this work for me:

@ControllerAdvice(annotations = RestController.class)
public class AnnotationAdvice {

    @Autowired
    UserServiceImpl userService;

    @ModelAttribute("currentUser")
    public User getCurrentUser() {
       UserDetails userDetails = (UserDetails) 
       SecurityContextHolder.getContext().getAuthentication().getPrincipal();
       return userService.findUserByEmail(userDetails.getUsername());
}
}
Badou
  • 199
  • 2
  • 4
4

There is one issue that occurs with redirection when using @ModelAttribute or HandlerInterceptor approach. When the handler returns Redirect view, the model attributes added this way are appended as query parameters.

Another way to handle this situation is to create session-scoped bean, that can be autowired in base application controller, or explicitelly in every controller where access is needed.

Details about available scopes and usage can be found here.

qza
  • 599
  • 4
  • 7
  • Another way to avoid that issue is not to return string values directly as attributes, but put them in a HashMap or List instead, then return that map or list as the attribute: `@ModelAttribute(name="mymap") public Map getSettings()`, then in your view access the values by prefixing the map name ("mymap.key"). – Richard Osseweyer Mar 20 '20 at 15:29
1

if you need add some global variables that every view can resolve these variables, why not define into a properties or map? then use spring DI, refer to the view resolver bean. it is very useful,such as static veriable, e.g. resUrl

<property name="viewResolvers">
        <list>
            <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                <property name="viewClass"
                    value="org.springframework.web.servlet.view.JstlView" />
                <property name="attributes" ref="env" />
                <property name="exposeContextBeansAsAttributes" value="false" />
                <property name="prefix" value="${webmvc.view.prefix}" />
                <property name="suffix" value="${webmvc.view.suffix}" />
            </bean>
        </list>
    </property>
Arbore
  • 11
  • 1