在序列化方面,Spring会话范围的bean(控制器)和对服务的引用

时间:2021-09-26 20:00:27
  • a standard case - you have a controller (@Controller) with @Scope("session").
  • 标准案例 - 你有一个带@Scope(“会话”)的控制器(@Controller)。
  • 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
  • 例如,放入会话中的类通常应该实现Serializable,以便在服务器重新启动时可以物理存储它们
  • 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.
  • 如果控制器实现Serializable,这意味着它所引用的所有服务(其他spring bean)也将被序列化。它们通常是代理,引用了交易管理器,实体管理器工厂等。
  • 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.
  • 通过实现ApplicationContextAware,某些服务甚至控制器都不可能拥有对ApplicationContext的引用,因此这可以有效地意味着整个上下文被序列化。并且鉴于它拥有许多连接 - 即那些不能通过构思序列化的东西,它将在腐败状态下恢复。

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.

到目前为止,我大多忽略了这些问题。最近我想到通过静态实用程序类WebApplicationContextUtils声明我的所有spring依赖项瞬态并将它们返回到readResolve()中,以便在ThreadLocal中保存请求/ ServletContext。这很乏味,但它保证了,当对象被反序列化时,它的依赖关系将与当前应用程序上下文“保持同步”。

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.

请注意,在JSF中,托管bean(〜控制器)是有状态的(与基于操作的Web框架不同)。所以也许我的问题更多的是JSF,而不是spring-mvc。

6 个解决方案

#1


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)

在这个演示文稿中(大约1:14),发言者说通过提供非可序列化bean的代理在春季3.0解决了这个问题,该bean从当前应用程序上下文中获取实例(在反序列化时)

#2


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.

通常我只是在会话中存储'user'对象,也许还有一些bean用于身份验证等。而已。

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

使用aop代理查看用于在会话范围中配置某些用户数据的spring文档:

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

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

Hope that helps

希望有所帮助

#3


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).

然后Spring不会将裸MyService注入到IndexBean中,而是将序列化代理注入其中。 (我测试过,它起作用了)。

However, the spring documentation writes:

但是,spring文档写道:

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.

您不需要将 与作为单例或原型作用域的bean结合使用。如果您尝试为单例bean创建范围代理,则会引发BeanCreationException。

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.

至少在使用基于java的配置时,可以很好地实例化bean及其代理,即不抛出异常。但是,看起来使用范围代理来实现可串行化并不是这种代理的预期用途。因此我担心Spring可能会修复“bug”并阻止通过基于Java的配置创建范围代理。

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.

此外,还有一个限制:重新启动Web应用程序后代理的类名称不同(因为代理的类名基于用于构造它的建议的哈希码,而这又取决于hashCode的一个拦截器的类对象.Class.hashCode不会覆盖Object.hashCode,它在重启时不稳定)。因此,其他VM或重新启动时不能使用序列化会话。

#4


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.

我最近将JSF与Spring结合起来。我使用RichFaces和@KeepAlive功能,它将支持页面的JSF bean序列化。我有两种方法可以让它发挥作用。

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

1)在JSF支持bean上使用@Component(“session”)

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

2)在需要时从ELContext获取bean,如下所示:

@SuppressWarnings("unchecked")
public static <T> T  getBean(String beanName) {
    return (T) FacesContext.getCurrentInstance().getApplication().getELResolver().getValue(FacesContext.getCurrentInstance().getELContext(), null, beanName);
}

#5


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.

在尝试了所有不同的替代方案后,我所要做的就是将aop:scoped-proxy添加到我的bean定义中并开始工作。

<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.

securityService被注入到我的managedbean中,这是一个视图作用域。这似乎工作正常。根据spring文档,这应该抛出BeanCreationException,因为securityService是一个单例。然而,这似乎没有发生,它工作正常。不确定这是一个错误或副作用是什么。

#6


1  

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

即使在不同的JVM之间,动态代理的序列化也很有效。用于会话复制。

@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))

您只需在刷新上下文之前设置ApplicationContext的id(请参阅: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

适用于Spring-Version:4.1.2.RELEASE

#1


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)

在这个演示文稿中(大约1:14),发言者说通过提供非可序列化bean的代理在春季3.0解决了这个问题,该bean从当前应用程序上下文中获取实例(在反序列化时)

#2


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.

通常我只是在会话中存储'user'对象,也许还有一些bean用于身份验证等。而已。

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

使用aop代理查看用于在会话范围中配置某些用户数据的spring文档:

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

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

Hope that helps

希望有所帮助

#3


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).

然后Spring不会将裸MyService注入到IndexBean中,而是将序列化代理注入其中。 (我测试过,它起作用了)。

However, the spring documentation writes:

但是,spring文档写道:

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.

您不需要将 与作为单例或原型作用域的bean结合使用。如果您尝试为单例bean创建范围代理,则会引发BeanCreationException。

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.

至少在使用基于java的配置时,可以很好地实例化bean及其代理,即不抛出异常。但是,看起来使用范围代理来实现可串行化并不是这种代理的预期用途。因此我担心Spring可能会修复“bug”并阻止通过基于Java的配置创建范围代理。

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.

此外,还有一个限制:重新启动Web应用程序后代理的类名称不同(因为代理的类名基于用于构造它的建议的哈希码,而这又取决于hashCode的一个拦截器的类对象.Class.hashCode不会覆盖Object.hashCode,它在重启时不稳定)。因此,其他VM或重新启动时不能使用序列化会话。

#4


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.

我最近将JSF与Spring结合起来。我使用RichFaces和@KeepAlive功能,它将支持页面的JSF bean序列化。我有两种方法可以让它发挥作用。

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

1)在JSF支持bean上使用@Component(“session”)

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

2)在需要时从ELContext获取bean,如下所示:

@SuppressWarnings("unchecked")
public static <T> T  getBean(String beanName) {
    return (T) FacesContext.getCurrentInstance().getApplication().getELResolver().getValue(FacesContext.getCurrentInstance().getELContext(), null, beanName);
}

#5


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.

在尝试了所有不同的替代方案后,我所要做的就是将aop:scoped-proxy添加到我的bean定义中并开始工作。

<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.

securityService被注入到我的managedbean中,这是一个视图作用域。这似乎工作正常。根据spring文档,这应该抛出BeanCreationException,因为securityService是一个单例。然而,这似乎没有发生,它工作正常。不确定这是一个错误或副作用是什么。

#6


1  

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

即使在不同的JVM之间,动态代理的序列化也很有效。用于会话复制。

@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))

您只需在刷新上下文之前设置ApplicationContext的id(请参阅: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

适用于Spring-Version:4.1.2.RELEASE