就序列化而言,Spring 会话范围的 bean(控制器)和对服务的引用

Posted

技术标签:

【中文标题】就序列化而言,Spring 会话范围的 bean(控制器)和对服务的引用【英文标题】:Spring session-scoped beans (controllers) and references to services, in terms of serialization 【发布时间】:2011-03-11 23:48:39 【问题描述】: 一个标准案例 - 你有一个控制器 (@Controller) 和 @Scope("session")。 会话中的类通常需要实现Serializable,以便在服务器重启时可以物理存储它们,例如 如果控制器实现Serializable,这意味着它所引用的所有服务(其他spring bean)也将被序列化。它们通常是代理,引用事务管理器、实体管理器工厂等。 通过实现ApplicationContextAware,某些服务甚至控制器持有对ApplicationContext 的引用并非不可能,因此这实际上意味着整个上下文是序列化的。并且鉴于它拥有许多连接 - 即无法通过想法序列化的事物,它将在损坏状态下恢复。

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

是否有任何公认的做法,或序列化部分弹簧上下文的任何指南。

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

【问题讨论】:

【参考方案1】:

我希望将控制器范围设置为“单例”,即每个应用程序一次,而不是在会话中。

会话范围通常更多地用于存储每个用户的信息或每个用户的功能。

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

查看 spring 文档,使用 aop 代理在会话范围内配置一些用户数据:

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

希望有帮助

【讨论】:

有时也有作用域控制器是有意义的。不经常,但有时。 我的假设是控制器通常具有请求范围,因为您在那里存储了一些每个请求的信息(即不是单例)。 只是好奇,我从未使用过会话范围的控制器——它在哪些情况下有用? @Bozho,传统上控制器/servlet应该没有状态 @matt b - 在 spring-mvc 和传统的基于动作的框架中,是的,我同意。但在 JSF 中并非如此。我更新了我的问题以包括 JSF【参考方案2】:

在this presentation(大约 1:14)中,演讲者说这个问题在 spring 3.0 中通过提供不可序列化 bean 的代理得到解决,该代理从 当前 应用程序上下文中获取实例(关于反序列化)

【讨论】:

找到了! infoq.com/presentations/Whats-New-in-Spring-3.0 滚动 1 小时进入电影 太棒了。它准确地解释了这个问题。并且解决了。即使没有可配置。我编辑了您的答案以表明这一点。 也许我做错了什么,但如果我尝试序列化 SpringBean,我会在下游 DataSource(或其他不可序列化的类)上得到 NotSeralizable 异常。我看了视频,他声称它“有效”。但我在 Spring 3.0.3 中没有看到魔法。 序列化数据源(例如数据库连接)没有多大意义,我认为。考虑当它在“另一边”反序列化时会发生什么。那台机器还会连接到数据库吗? 不确定我在这里做错了什么...我有一个用@Scope(value="session", proxyMode = ScopedProxyMode.TARGET_CLASS) 注释的托管bean。如果 DAO 被标记为瞬态,则反序列化后为空。如果没有瞬态,则在序列化时会为 DAO 抛出 NotSerializableException。【参考方案3】:

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

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

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

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

【讨论】:

【参考方案4】:

赏金似乎没有得到一个答案,所以我将记录我的有限理解:

@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 不会将裸 MyService 注入 IndexBean,而是将它的可序列化代理注入。 (我对此进行了测试,它确实有效)。

不过,spring 文档writes:

您确实需要将&lt;aop:scoped-proxy/&gt; 与范围为singletonsprototypes 的bean 结合使用。如果您尝试为单例 bean 创建作用域代理,则会引发 BeanCreationException

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

另外,还有一个限制:web应用重启后proxy的类名不同(因为proxy的类名是基于构建它的advice的hashcode,而这又取决于拦截器类对象的 hashCode。Class.hashCode 不会覆盖 Object.hashCode,它在重启后不稳定)。因此,序列化的会话不能被其他虚拟机使用,也不能跨重启使用。

【讨论】:

感谢您分享有限的理解。我想知道如何为所有单例服务默认 @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) 注释?【参考方案5】:

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

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

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

【讨论】:

这里我遇到了同样的问题并以同样的方式解决了:***.com/questions/9986197/…【参考方案6】:

Dynamic-Proxies 的序列化效果很好,即使在不同的 JVM 之间也是如此,例如。用于会话复制。

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

.....

您只需要在刷新上下文之前设置 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();

在 Spring 版本上运行良好:4.1.2.RELEASE

【讨论】:

以上是关于就序列化而言,Spring 会话范围的 bean(控制器)和对服务的引用的主要内容,如果未能解决你的问题,请参考以下文章

Spring会话范围的bean作为原型bean中的依赖项?

如何设置 Spring 会话范围 bean 的属性名称?

是否可以使用会话中的属性配置 Spring 会话范围的 bean?

无法反序列化 Spring Session Scoped bean

Spring 3 MVC:在 MVC 控制器方法参数中公开会话范围的 bean

带有 AOP 的 Spring 会话范围 bean 中的问题