就序列化而言,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:
您确实不需要将
<aop:scoped-proxy/>
与范围为singletons
或prototypes
的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?
无法反序列化 Spring Session Scoped bean