23

In Spring MVC, it is easy to bind request parameter to method paramaters handling the request. I just use @RequestParameter("name"). But can I do the same with request attribute? Currently, when I want to access request attribute, I have to do following:

MyClass obj = (MyClass) request.getAttribute("attr_name");

But I really would like to use something like this instead:

@RequestAttribute("attr_name") MyClass obj

Unfortunately, it doesn't work this way. Can I somehow extend Spring functionality and add my own "binders"?

EDIT (what I'm trying to achieve): I store currently logged user inside request attribute. So whenever I want to access currently logged user (which is pretty much inside every method), I have to write this extra line user = (User) request.getAttribute("user");. I would like to make it as short as possible, preferably inject it as a method parameter. Or if you know another way how to pass something across interceptors and controllers, I would be happy to hear it.

kryger
  • 12,906
  • 8
  • 44
  • 65
tobik
  • 7,098
  • 7
  • 41
  • 53

7 Answers7

36

Well, I finally understood a little bit how models work and what is @ModelAttribute for. Here is my solution.

@Controller 
class MyController
{
    @ModelAttribute("user")
    public User getUser(HttpServletRequest request) 
    {
        return (User) request.getAttribute("user");
    }

    @RequestMapping(value = "someurl", method = RequestMethod.GET)
    public String HandleSomeUrl(@ModelAttribute("user") User user)  
    {
        // ... do some stuff
    }
}

The getUser() method marked with @ModelAttribute annotation will automatically populate all User user parameters marked with @ModelAttribute. So when the HandleSomeUrl method is called, the call looks something like MyController.HandleSomeUrl(MyController.getUser(request)). At least this is how I imagine it. Cool thing is that user is also accessible from the JSP view without any further effort.

This solves exactly my problem however I do have further questions. Is there a common place where I can put those @ModelAttribute methods so they were common for all my controllers? Can I somehow add model attribute from the inside of the preHandle() method of an Interceptor?

tobik
  • 7,098
  • 7
  • 41
  • 53
  • If your parameter names match properties on your model object, you dont need the additional getUser method, spring will set those properties automatically – chrismarx Feb 07 '14 at 21:33
  • 1
    In this way the User object in your method will not only be retrieved from the view model, but also its properties will be bound (and overwritten) by incoming request- or form parameters. If, for example, the User object has an id property (which many user objects will have) and the request url or form also have a unrelated) id parameter, it will overwrite the id of the User object. This has once created a nasty bug in my code. In this particular case, you do not want the form binding properties of the @ModelAttribute. An alternative is using `User u = (User) model.get("user");` – klausch Nov 03 '15 at 10:06
14

Use (as of Spring 4.3) @RequestAttribute:

@RequestMapping(value = "someurl", method = RequestMethod.GET)
public String handleSomeUrl(@RequestAttribute User user) {
    // ... do some stuff
}

or if the request attribute name does not match the method parameter name:

@RequestMapping(value = "someurl", method = RequestMethod.GET)
public String handleSomeUrl(@RequestAttribute(name="userAttributeName") User user) {
    // ... do some stuff
}
Brice Roncace
  • 10,110
  • 9
  • 60
  • 69
3

I think what you are looking for is:

@ModelAttribute("attr_name") MyClass obj

You can use that in the parameters for a method in your controller.

Here is a link a to question with details on it What is @ModelAttribute in Spring MVC?

That question links to the Spring Documentation with some examples of using it too. You can see that here

Update

I'm not sure how you are setting up your pages, but you can add the user as a Model Attribute a couple different ways. I setup a simple example below here.

@RequestMapping(value = "/account", method = RequestMethod.GET)
public ModelAndView displayAccountPage() {
    User user = new User(); //most likely you've done some kind of login step this is just for simplicity
    return new ModelAndView("account", "user", user); //return view, model attribute name, model attribute
}

Then when the user submits a request, Spring will bind the user attribute to the User object in the method parameters.

@RequestMapping(value = "/account/delivery", method = RequestMethod.POST)
public ModelAndView updateDeliverySchedule(@ModelAttribute("user") User user) {

    user = accountService.updateDeliverySchedule(user); //do something with the user

    return new ModelAndView("account", "user", user);
}
Community
  • 1
  • 1
ssn771
  • 1,270
  • 2
  • 17
  • 20
  • Thanks, but I was trying to achieve something else.. maybe I should have explained that. I use request attribute to store currently logged user. So just as you call `User user = (User) session.getAttribute("user");` I call `user = (User) request.getAttribute("user");`. But it's quite annoying to write that in every single method. I would like to make this as short as possible, preferably inject it as the method parameter. – tobik Feb 22 '13 at 20:39
  • You can still use this method to do that. You just need to add the User object to the model. Then you can access it. There are a couple ways to do that. I'll update my answer. – ssn771 Feb 22 '13 at 23:06
  • I am not as familiar with `@SessionAttributes` but that might work for you too. Here is a [link](http://static.springsource.org/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-sessionattrib) to the documentation on it. – ssn771 Feb 22 '13 at 23:28
  • Well, I'm not sure if I understand you but you definitely put me in the right direction and I found what I've been looking for. So thanks and see my own answer in few moments :) – tobik Feb 23 '13 at 00:30
2

Not the most elegant, but works at least...

@Controller
public class YourController {

    @RequestMapping("/xyz")
    public ModelAndView handle(
        @Value("#{request.getAttribute('key')}") SomeClass obj) {

        ...
        return new ModelAndView(...);
    }

}

Source : http://blog.crisp.se/tag/requestattribute

Peter Szanto
  • 7,568
  • 2
  • 51
  • 53
1

From spring 3.2 it can be done even nicer by using Springs ControllerAdvice annotation. This then would allow you to have an advice which adds the @ModelAttributes in a separate class, which is then applied to all your controllers.

For completeness, it is also possible to actually make the @RequestAttribute("attr-name") as is. (below modified from this article to suit our demands)

First, we have to define the annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface RequestAttribute {
    String value();
}

Then we need a [WebArgumentResolver] to handle what needs to be done when the attribute is being bound

public class RequestAttributeWebArgumentResolver implements WebArgumentResolver {

    public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest nativeWebRequest) throws Exception {
        // Get the annotation       
        RequestAttribute requestAttributeAnnotation = methodParameter.getParameterAnnotation(RequestAttribute.class);

        if(requestAttributeAnnotation != null) {
            HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest();

            return request.getAttribute(requestAttributeAnnotation.value);
        }

        return UNRESOLVED;
    }
}

Now all we need is to add this customresolver to the config to resolve it:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="customArgumentResolver">
        <bean class="com.sergialmar.customresolver.web.support.CustomWebArgumentResolver"/>
    </property>
</bean>

And we're done!

Koos Gadellaa
  • 1,220
  • 7
  • 17
0

Yes, you can add your own 'binders' to the request attribute - see spring-mvc-3-showcase, or use @Peter Szanto's solution.

Alternatively, bind it as a ModelAttribute, as recommended in other answers.

As it's the logged-in user that you want to pass into your controller, you may want to consider Spring Security. Then you can just have the Principle injected into your method:

@RequestMapping("/xyz")
public String index(Principal principle) {
    return "Hello, " + principle.getName() + "!";
}
whistling_marmot
  • 3,561
  • 3
  • 25
  • 39
0

In Spring WebMVC 4.x, it prefer implements HandlerMethodArgumentResolver

@Override
public boolean supportsParameter(MethodParameter parameter) {
    return parameter.getParameterAnnotation(RequestAttribute.class) != null;
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    return webRequest.getAttribute(parameter.getParameterAnnotation(RequestAttribute.class).value(), NativeWebRequest.SCOPE_REQUEST);
}

}

Then register it in RequestMappingHandlerAdapter

davidwang
  • 1
  • 1