Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implicit creation of model (session) attribute [SPR-4408] #9086

Closed
spring-projects-issues opened this issue Jan 30, 2008 · 13 comments
Closed
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket)

Comments

@spring-projects-issues
Copy link
Collaborator

Andreas Schildbach opened SPR-4408 and commented

If you declare a model attribute with @ModelAttribute in a handler method (parameter annotation), it would be convenient if the framework would construct an empty attribute for you, if there isn't one already and there is a no-arg constructor on the attribute's class.

Instead of code like this

@RequestMapping
public String setupForm(ModelMap model) {
Command command = new Command();
command.setXyz(defaultValue);
model.addAttribute("command", command);
return view;
}

you could just write

@RequestMapping
public String setupForm(@ModelAttribute Command command) {
command.setXyz(defaultValue);
return view;
}

This feature would of course apply to @SessionAttributes, too.

If this feature looks too magical, you could also introduce a boolean parameter on the @ModelAttribute annotation, saying whether implicit creation is allowed or not.


Affects: 2.5.1

9 votes, 10 watchers

@spring-projects-issues
Copy link
Collaborator Author

Marten Deinum commented

Wouldn't this also be achieved by the following

@ModelAttribute("command")
public Command createCommand() {
return new Command();
}

@RequestMapping
public String setupForm(@ModelAttribute Command command) {
command.setXyz(defaultValue);
return view;
}

@spring-projects-issues
Copy link
Collaborator Author

Andreas Schildbach commented

If I understand the mechanism correctly, your @ModelAttribute("command") would create a new Command() all the time, overwriting your form data (for example). I'd want a new Command() only created if none is present.

@spring-projects-issues
Copy link
Collaborator Author

Bartek commented

I've got the same problem (discussed deeply on forum http://forum.springsource.org/showthread.php?p=211420)

Shortly speaking I would like to implement the following scenario (all requests go to the same url ):

  • first GET: create command and display form view
  • POST: execute business action and redisplay the same form view
  • later GETs: display form view (reuse previously existing command)

I've tried different solutions:

  1. method annotated with @ModelAttribute - does not work, because is executed on each request (create new command instance each time)
  2. @ModelAttribute annotated parameter in "GET" method - does not work in case when command is not present in session (when first time url is accessed)
  3. in "GET" method checking of existence of 'command' in model - does not work, because session attribute is not available in model when the method is processed (unless explicitly marked as parameter with @ModelAttribute annotation - but we back to point 2)

In my opinion the @ModelAttribute annotation used on method level should not be executed if connected model attribute marked as @SessionAttribute and is already present in session.

in other words,. method:

@ModelAttribute("command")
public Command createCommand() {
return new Command();
}

should not be executed if attribute "command" is already available in session.

@spring-projects-issues
Copy link
Collaborator Author

Jeremy Haile commented

We are using Spring 2.5.1 and experiencing this problem. I won't expand too much since Bartek summarized our problem exactly in his last comment.

One of our usages is that we want to have a GET method whose parameters are bound to a command object, but the command is stored in the session so that later GETs will retain the previous settings (although params would overwrite them)

For example:
@SessionAttributes("command")
public class UsersController

public void browseUsers( @ModelAttribute("command") sortCommand ) { }

This does not work at all now for the reasons described by Bartek.

I will also mention that even though the solution he described (not executing a @ModelAttribute method for an attribute listed in @SessionAttribute if it already exists in the session) would work, I would also like to see the @ModelAttribute object automatically populated and stored in the session if it doesn't already exist.

Why should I have to write:
@ModelAttribute("command")
public Command buildCommand() {
return new Command();
}
public void browseUsers(@ModelAttribute("command") command ) {}

when I could just write:
public void browseUsers(@ModelAttribute("command") command ) {}

and let Spring create the command, bind to it, and store it in the session for me?

@spring-projects-issues
Copy link
Collaborator Author

Phil Krasko commented

Is there any workaround for this?

I have a bunch of pages using a large DTO that I would like to store as a @SessionAttribute. These pages all have the same DTO identifier in the query string and are all being accessed by a HTTP_GET.

This functionality would definitely be useful!

@spring-projects-issues
Copy link
Collaborator Author

Alex Savitsky commented

The only workaround so far is to replace the method resolveModelAttribute in class org.springframework.web.bind.annotation.support.HandlerMethodInvoker, to create a new attribute if one's not found in session. Specifically, replace this

if (bindObject == null) {
     raiseSessionRequiredException("Session attribute '" + name + "' required - not found in session");
}

with this

if (bindObject == null) {
     bindObject = BeanUtils.instantiateClass(paramType);
     this.sessionAttributeStore.storeAttribute(webRequest, name, bindObject);
}

@spring-projects-issues
Copy link
Collaborator Author

Alex Savitsky commented

yes, and I'm using Spring 3, it haven't been fixed there, either. I'd even classify this as a bug fix, not improvement, as this behavior makes much more sense than the original one.

@spring-projects-issues
Copy link
Collaborator Author

Phil Krasko commented

Yes, that's a viable way to fix it.
I actually created an interceptor to do this for me so I didn't have to modify the spring core classes. It's a bit of a hack but it works great.

-spring 2.5.6

@spring-projects-issues
Copy link
Collaborator Author

Alex Savitsky commented

Could you please share the interceptor code and config? I'd rather leave the core classes alone, too, just didn't know of that possibility.

@spring-projects-issues
Copy link
Collaborator Author

Phil Krasko commented

Alex,

I actually tried to tackle 2 issues in what I created.

Issue #1 is described above in this ticket.
Issue #2:
I wanted to figure out a way where I could store an object in the session and have better control to when it will be cleaned up.
An example would be when you have a multi-page wizard (assume all in 1 controller). Assume you have ajax posts to other controllers during this wizard which also require access to the session attribute. You don't want any successful post to clear the session.

The interceptor I have accepts a new annotation I created called "@ControllerAttributes". This is the be used in conjunction with @SessionAttributes and allows you to specify an array of controllers where the @SessionAttributes will still be valid and allows you to default initialize your SessionAttributes (and even provides a ModelStatus so your handler knows it was default initialized).

Example:

@SessionAttributes(values = {"person"}, types = {Person.class})
@ControllerAttributes(initialize = true, controllers = {SecondController.class})
public class FirstController{

@RequestMapping(method = RequestMethod.GET)
public String handler(@ModelAttribute("person")Person person, ModelStatus modelStatus){
if(ModelStatus.getStatus() == ModelStatus.STATUS.DEFAULT_INITIALIZED){
// I can further initialize this model through the db if I want.
}
}

}

A quick explanation of what happens:
The interceptor will pick up @ControllerAttributes annotation. If is detects initialize = true (default setting) it will initialize all Session Attributes prior to invoking the handler. This solves the problem above.
It will also automatically kill the session attributes if it detects a request going to a different controller (unless specified in the controllers array within @ControllerAttributes). This means I no longer have to use Status status.setComplete(), which in my mind is too easy to forget and may cause abnormal session memory size.

Let me know if you're interested and maybe you can send me you email address. I really would like another eye to go through the code. I created in in one day and only tested it on 2 controllers or so.

@spring-projects-issues
Copy link
Collaborator Author

Tamas Perlaky commented

Assuming that your session attribute store is actually the session (which I guess might not always be true?), you could check the session to see if it's already there before instantiating a new object:

@ModelAttribute()
public Something initSomething(HttpSession session)
{
  if (session.getAttribute("something") != null)
    return null;
  else
    return new Something();
}

I know the initial assumption may not be valid, but how dangerous would this really be in practice?

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

This ticket has accumulated quite a few comments. I will try to summarize and see if there is anything unresolved.

The original description was to fall back on the no-arg constructor for @ModelAttribute args. That certainly works. It is also said "the feature should apply to @SessionAttributes too". Consider the scenario described by Bartek:

  • first GET: create command and display form view
  • POST: execute business action and redisplay the same form view
  • later GETs: display form view (reuse previously existing command)

That should work as well using a solution similar to this:

@SessionAttributes(types=Command.class)
@Controller
public class CommandController {

  @ModelAttribute
  public Command createCommand(@PathVariable long id) {
    // retrieve command ...
    return command;
  }

  @RequestMapping(value="/{id}/edit", method=GET)
  public void edit() {
  }

  @RequestMapping(value="/{id}", method=PUT)
  public String update(@ModelAttribute Command command) {
    // do something with command ...
    return "edit";
  }
}

With that in mind is there anything that remains unresolved?

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

Closing this issue for now without any further comments or use cases to consider.

@spring-projects-issues spring-projects-issues added type: enhancement A general enhancement in: web Issues in web modules (web, webmvc, webflux, websocket) labels Jan 11, 2019
@spring-projects-issues spring-projects-issues removed the type: enhancement A general enhancement label Jan 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket)
Projects
None yet
Development

No branches or pull requests

2 participants