View Full Version : refresh issue - command object becomes null
KENTOSI
Sep 12th, 2007, 01:08 AM
Hi all,
I am having an unusual problem with my Spring MVC setup and it's doing my head in. It seems that when I refresh a page loaded by a form-controller the command object no longer exists in the session.
Let me explain:
I have Page1FormController and Page2FormController. Both extend the SimpleFormController with the command object being an "application" (ie - an Application object).
So with Page1FormController I have:
public class Page1FormController extends SimpleFormController {
public Page1FormController () {
super.setCommandName("application");
super.setSessionForm(true);
}
...
protected Object formBackingObject(HttpServletRequest request) throws ServletException {
// Create a new application here.
Application application = new Application();
return application;
}
protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception {
...
// Place application in session to access on second page.
request.getSession().setAttribute("application", application);
return new ModelAndView("redirect:/page2FormController.htm");
}
}
And then in Page2FormController I have:
public class Page2FormController extends SimpleFormController {
public Page2FormController() {
super.setCommandName("application");
super.setSessionForm(true);
}
...
protected Object formBackingObject(HttpServletRequest request) throws ServletException {
// This works when I come from page1, but fails when i'm already on
// page2 and hit the browser refresh button:
Application application = (Application) session.getAttribute("application");
logger.debug("application = " + application);
return application;
}
}
Now the problem is that when i get to page2FormController.htm the "application" object exists in the session. But when I refresh that page it's somehow disappeared (null). :confused:
I don't see how coming from page1 can make such a difference since it's a redirect i'm performing anyway.
KENTOSI
Sep 16th, 2007, 07:29 PM
i've come up with a temporary solution which seems to do the trick. it's really a hack, but until someone can help me i guess i'll have to stick with this.
In Page2FormController:
protected Object formBackingObject(HttpServletRequest request) throws ServletException {
Application application = (Application) session.getAttribute("application");
// This is the hack.
if (application != null) {
request.getSession().setAttribute("application2", application);
} else {
Application application2 = (Application) session.getAttribute("application2");
application = application2;
}
return application;
}
Basically, I protect the application session variable by storing it as another name (application2) and retrieve that when application is null.
lumpynose
Sep 16th, 2007, 11:47 PM
You could also set up a bean that has session scope.
http://static.springframework.org/spring/docs/2.0.x/reference/beans.html#beans-factory-scopes
I just did this and it's quite slick. The bean is injected by spring and its internal state is kept in the session so each object that gets it injected gets what's in it. The first object (e.g., some controller) that gets it can set it up, calling its setters, then the rest can use its getters and setters.
applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.1.xsd">
<bean id="userMeta" class="jmemento.domain.user.impl.UserMeta" scope="session">
<aop:scoped-proxy proxy-target-class="false" />
</bean>
myApp-servlet.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsM ultipartResolver">
</bean>
<!--
## convention over configuration handler
-->
<bean
class="org.springframework.web.servlet.mvc.support.Contro llerClassNameHandlerMapping" />
<!--
## controller beans
-->
<bean class="jmemento.web.controller.user.UserHomeController">
<constructor-arg ref="userMeta" />
</bean>
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>myApp</display-name>
<!--
## Location of the Log4J config file, for initialization and
## refresh checks. Applied by Log4jConfigListener.
-->
<context-param>
<param-name>
log4jConfigLocation
</param-name>
<param-value>
/WEB-INF/log4j.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.util.Log4jConfigListener
</listener-class>
</listener>
<!-- bootstraps the WebApplicationContext -->
<!-- must come after the Log4jConfigListener -->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListe ner
</listener-class>
</listener>
<!-- listener for beans with session scope -->
<listener>
<listener-class>
org.springframework.web.context.request.RequestCon textListener
</listener-class>
</listener>
UserHomeController.java:
public final class UserHomeController extends AbstractCommandController {
private final transient Log log = LogFactory.getLog(getClass());
private final IUserMeta userMetaSession;
/**
* @param _userMetaSession
*/
public UserHomeController(final IUserMeta _userMetaSession) {
if (_userMetaSession == null)
throw (new IllegalArgumentException("userMetaSession can't be null"));
userMetaSession = _userMetaSession;
setSupportedMethods(new String[] {
METHOD_GET
});
// no longer used
setCommandClass(UserMeta.class);
}
@Override
protected ModelAndView handle(final HttpServletRequest request,
final HttpServletResponse response, final Object command,
final BindException errors) throws Exception {
// no longer using the command
log.debug(String.format("command: %s", command));
log.debug(String.format("user: %s", userMetaSession));
if (userMetaSession.getId() == null)
return (new ModelAndView("notsignedin"));
final ModelAndView mav = new ModelAndView();
mav.addObject("user", userMetaSession);
return (mav);
}
}
KENTOSI
Sep 17th, 2007, 08:00 PM
Very interesting solution you've got there lumpynose, i'll have to try it out today.
I'm assuming that because you've declared "userMeta" as a session scoped bean then "UserHomeController" (which relies on userMeta in its constructor) is no longer a singleton and hence gets instantiated with every new session. I hope I'm correct here.
lumpynose
Sep 17th, 2007, 08:13 PM
Very interesting solution you've got there lumpynose, i'll have to try it out today.
I'm assuming that because you've declared "userMeta" as a session scoped bean then "UserHomeController" (which relies on userMeta in its constructor) is no longer a singleton and hence gets instantiated with every new session. I hope I'm correct here.
Read the section of the reference manual I linked to. If I remember correctly the UserHomeController is still a singleton but the userMetaSession bean is wrapped by a proxy and its methods return the correct values for the session, because it's the proxy's methods that are really being used. That's my rough understanding of how it works.
Jörg Heinicke
Sep 18th, 2007, 12:09 AM
Read the section of the reference manual I linked to. If I remember correctly the UserHomeController is still a singleton but the userMetaSession bean is wrapped by a proxy and its methods return the correct values for the session, because it's the proxy's methods that are really being used. That's my rough understanding of how it works.
Just want to confirm your understanding :) Since the session-scoped proxy is a singleton the controller can remain a singleton as well. The proxy works internally with ThreadLocals, so there is no threading issue.
Joerg
vBulletin® v3.7.3, Copyright ©2000-2008, Jelsoft Enterprises Ltd.