Spring session-scoped beans (controllers) and references to services, in terms of serialization
Asked Answered
K

6

44
  • a standard case - you have a controller (@Controller) with @Scope("session").
  • classes put in the session usually are expected to implement Serializable so that they can be stored physically in case the server is restarted, for example
  • If the controller implements Serializable, this means all services (other spring beans) it is referring will also be serialized. They are often proxies, with references to transaction mangers, entity manager factories, etc.
  • It is not unlikely that some service, or even controller, hold a reference to the ApplicationContext, by implementing ApplicationContextAware, so this can effectively mean that the whole context is serialized. And given that it holds many connections - i.e. things that are not serializable by idea, it will be restored in corrupt state.

So far I've mostly ignored these issues. Recently I thought of declaring all my spring dependencies transient and getting them back in readResolve() by the static utility classes WebApplicationContextUtils and such that hold the request/ServletContext in a ThreadLocal. This is tedious, but it guarantees that, when the object is deserialized, its dependencies will be "up to date" with the current application context.

Is there any accepted practice for this, or any guidelines for serializing parts of the spring context.

Note that in JSF, managed beans (~controllers) are stateful (unlike action-based web frameworks). So perhaps my question stands more for JSF, than for spring-mvc.

Kellum answered 5/7, 2010 at 16:27 Comment(0)
B
19

In this presentation (around 1:14) the speaker says that this issue is resolved in spring 3.0 by providing a proxy of non-serializable beans, which obtains an instance from the current application context (on deserialization)

Beastly answered 5/7, 2010 at 19:42 Comment(6)
Found it! infoq.com/presentations/Whats-New-in-Spring-3.0 Scroll 1 hour into the movieBeastly
Great. It explains exactly this issue. and it is resolved. Even without Configurable. I edited your answer to indicate that.Kellum
Maybe I'm doing something wrong, but if I try to serialize a SpringBean I get a NotSeralizable exception on the downstream DataSource (or other unserializable class). I watched the video, he claims it "just works". But I'm not seeing magic here in Spring 3.0.3.Expanse
serializing a datasource (eg a database connection) doesn't make much sense, I think. Consider what would happen when it is deserialized on 'the other side'. Would that machine still have a connection to the database?Beastly
Not sure what I'm doing wrong here either...I have a managed bean annotated with @Scope(value="session", proxyMode = ScopedProxyMode.TARGET_CLASS). If the DAO is marked as transient, it is null after deserialization. Without transient, a NotSerializableException is thrown for the DAO when serializing.Mcanally
Because noone have posted here code sample, I've found this link: jcavallotti.blogspot.de/2011/06/spring-aop-scoped-proxies.htmlKerk
T
7

I would expect to scope controllers as 'singleton', i.e. once per application, rather than in the session.

Session-scoping is typically used more for storing per-user information or per-user features.

Normally I just store the 'user' object in the session, and maybe some beans used for authentication or such. That's it.

Take a look at the spring docs for configuring some user data in session scope, using an aop proxy:

http://static.springsource.org/spring/docs/2.5.x/reference/beans.html#beans-factory-scopes-other-injection

Hope that helps

Typhoid answered 5/7, 2010 at 16:42 Comment(6)
Sometimes it makes sense to have scoped controllers also. Not often, but sometimes.Sue
my assumption is that controllers are often of request scope, as you store some per-request information there (i.e. not singleton).Kellum
Just curious, I've never used a session-scoped controller - what are some of the cases where it is useful?Imponderabilia
@Bozho, traditionally controllers/servlets should have no stateImponderabilia
@matt b - in spring-mvc, and traditional action-based frameworks, yes, I agree. But in JSF it's not that way. I updated my question to include JSFKellum
Hi Bozho. It's nice to store as little as possible in the session, firstly as you mentioned about needing to make all your serializable, but also regarding session persistence. When you release a new version of your servlet, it's a pain to deal with invalidated sessions due to altered classes, such as services, controllers etc. It's nice to stick to a few pojos (such as a User class), which shouldnt change very often.Typhoid
F
7

It appears that bounty didn't attract a single answer, so I'll document my limited understanding:

@Configuration
public class SpringConfig {

    @Bean 
    @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) 
    MyService myService() {
        return new MyService();
    }

    @Bean
    @Scope("request")
    public IndexBean indexBean() {
        return new IndexBean();
    }

    @Bean
    @Scope("request")
    public DetailBean detailBean() {
        return new DetailBean();
    }
}

public class IndexBean implements Serializable {

    @Inject MyService myService;

    public void doSomething() {
        myService.sayHello();
    }
}

public class MyService {
    public void sayHello() {
        System.out.println("Hello World!");
    }
}

Spring will then not inject the naked MyService into IndexBean, but a serializable proxy to it. (I tested that, and it worked).

However, the spring documentation writes:

You do not need to use the <aop:scoped-proxy/> in conjunction with beans that are scoped as singletons or prototypes. If you try to create a scoped proxy for a singleton bean, the BeanCreationException is raised.

At least when using java based configuration, the bean and its proxy can be instantiated just fine, i.e. no Exception is thrown. However, it looks like using scoped proxies to achieve serializability is not the intended use of such proxies. As such I fear Spring might fix that "bug" and prevent the creation of scoped proxies through Java based configuration, too.

Also, there is a limitation: The class name of the proxy is different after restart of the web application (because the class name of the proxy is based on the hashcode of the advice used to construct it, which in turn depends on the hashCode of an interceptor's class object. Class.hashCode does not override Object.hashCode, which is not stable across restarts). Therefore the serialized sessions can not be used by other VMs or across restarts.

Finite answered 20/1, 2012 at 20:49 Comment(1)
Thanks for sharing your limited understanding. I was wondering how to default the @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) annotation for all singleton services?Foreknow
S
2

I recently combined JSF with Spring. I use RichFaces and the @KeepAlive feature, which serializes the JSF bean backing the page. There are two ways I have gotten this to work.

1) Use @Component("session") on the JSF backing bean

2) Get the bean from ELContext when ever you need it, something like this:

@SuppressWarnings("unchecked")
public static <T> T  getBean(String beanName) {
    return (T) FacesContext.getCurrentInstance().getApplication().getELResolver().getValue(FacesContext.getCurrentInstance().getELContext(), null, beanName);
}
Shepley answered 3/11, 2011 at 21:23 Comment(0)
S
2

After trying all the different alternatives suggested all I had to do was add aop:scoped-proxy to my bean definition and it started working.

<bean id="securityService"
    class="xxx.customer.engagement.service.impl.SecurityContextServiceImpl">
    <aop:scoped-proxy/>
    <property name="identityService" ref="identityService" />
</bean>

securityService is injected into my managedbean which is view scoped. This seems to work fine. According to spring documentation this is supposed to throw a BeanCreationException since securityService is a singleton. However this does not seems to happen and it works fine. Not sure whether this is a bug or what the side effects would be.

Sixtynine answered 26/1, 2012 at 14:27 Comment(1)
Here I had the same problem and solved in same way: #9986697Murage
A
1

Serialization of Dynamic-Proxies works well, even between different JVMs, eg. as used for Session-Replication.

@Configuration public class SpringConfig {
@Bean 
@Scope(proxyMode = ScopedProxyMode.INTERFACES) 
MyService myService() {
    return new MyService();
}
.....

You just have to set the id of the ApplicationContext before the context is refreshed (see: org.springframework.beans.factory.support.DefaultListableBeanFactory.setSerializationId(String))

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
// all other initialisation part ...
// before! refresh
ctx.setId("portal-lasg-appCtx-id");
// now refresh ..
ctx.refresh();
ctx.start();

Works fine on Spring-Version: 4.1.2.RELEASE

Argumentative answered 2/1, 2015 at 11:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.