从EJB异步方法访问CDI @SessionScoped/@ViewScoped Bean成员变量的注意事项
关于CDI会话Bean与视图Bean在EJB异步方法中的访问注意事项
好问题!咱们先从@SessionScoped Bean的场景说起,除了你提到的会话超时,还有这些容易被忽略的点:
@SessionScoped Bean的额外注意事项
- 会话钝化/活化的影响:如果容器因为内存压力等原因对
@SessionScopedBean进行钝化(序列化到磁盘),哪怕你传入的成员变量是线程安全的,也要确认它是否支持序列化。要是变量不可序列化,钝化过程会直接抛出异常;就算可序列化,异步方法持有的引用可能指向钝化前的实例,当Bean被活化(反序列化)后,这个旧引用和新实例的状态会不一致,导致操作失效。 - 事务上下文的隔离性:EJB的
@Asynchronous方法默认会启动新的独立事务,和调用它的原请求事务完全隔离。如果你的线程安全成员变量关联了事务性资源(比如绑定了某个持久化上下文),要注意异步操作的修改和原请求的事务提交顺序——比如原请求已经提交了数据,异步方法还在修改同一个资源,可能会出现业务逻辑上的不一致,哪怕线程安全也救不了。 - 会话上下文的主动销毁:除了超时,用户主动登出或者调用
HttpSession.invalidate()也会销毁会话上下文。如果异步方法在这之后才执行,虽然你持有的是成员变量的引用,但如果这个变量依赖会话上下文里的其他资源(比如其他@SessionScopedBean),就会触发上下文失效的异常,哪怕变量本身线程安全。 - 并发操作的语义一致性:就算成员变量是线程安全的(比如
ConcurrentHashMap),也要考虑业务逻辑上的并发语义。比如原请求在修改变量的某个值,异步方法同时修改另一个值,线程安全能保证数据不损坏,但你得确保这种并发修改符合业务规则——比如是否需要先执行原请求的修改,再执行异步操作?这时候线程安全解决不了顺序问题,可能需要额外的同步机制或者事务控制。
改用@ViewScoped Bean的差异与注意点
@ViewScoped(不管是JSF原生还是CDI的@ViewScoped)和@SessionScoped的生命周期绑定完全不同,这里的坑更多:
- 视图生命周期的强绑定:
@ViewScopedBean的生命周期和JSF视图严格绑定,只要用户导航到其他页面、刷新页面(非AJAX)或者视图超时,Bean就会被容器销毁。如果异步方法在这之后执行,哪怕你传了成员变量的引用,要么变量已经被回收导致NPE,要么操作的是已经失效的视图状态,完全没有业务意义。 - 上下文传播的局限性:EJB容器通常不支持传播JSF的
@ViewScoped上下文到异步方法中。也就是说,如果你在异步方法里除了操作传入的成员变量,还想访问@ViewScopedBean的其他成员,大概率会抛出上下文找不到的异常——因为异步方法运行时,原视图上下文已经不存在了。 - 视图序列化的约束:
@ViewScopedBean会被序列化到视图状态(客户端或服务器端存储),如果传入异步方法的成员变量不可序列化,会直接导致视图序列化失败,页面无法正常渲染。而且就算可序列化,当视图被活化时,异步方法持有的旧引用和新反序列化的实例状态会脱节,操作无效。 - 并发访问的风险更高:虽然
@ViewScopedBean在单个视图的请求中是线程安全的,但异步方法是在独立线程中运行的,和视图的请求线程完全隔离。如果多个异步方法同时操作同一个成员变量,哪怕变量线程安全,也可能因为视图被提前销毁,导致资源泄漏或者无效操作。
最后要提醒的是,不管用哪种Scope,最好在异步方法里先检查一下Bean的上下文是否还活跃(比如通过BeanManager判断上下文是否存在),避免做无用功或者抛出异常。
内容的提问来源于stack exchange,提问作者TownCube
相关产品推荐
相关产品推荐

