JSF – 会话范围的托管bean没有在会话反序列化上重新注入依赖项

我不确定我做的是不是错了,或者我错过了某个地方的注释或配置项。 这是情况:

我有一个JSF应用程序,其中包含一个名为SessionData的会话范围的bean。 这个bean在创建时注入了一个应用程序范围的bean引用(类型为ApplicationData )。 首次创建会话时,这可以正常工作。 dependency injection是使用faces-config.xml文件中的元素完成的,如下所示:

  sessionData my.package.SessionData session  applicationData my.package.ApplicationData #{applicationData}    applicationData my.package.ApplicationData application  

因为在序列化时我的SessionData对象包含ApplicationData对象没有意义,所以我在SessionData对象中将ApplicationData引用标记为瞬态:

 transient private ApplicationData applicationData; 

一切都很好,直到Web应用程序停止(在我的Tomcat 6.x容器中)并且会话被序列化。 当我重新启动应用程序并反序列化会话时,JSF不会重新注入我对ApplicationData引用。 我知道反序列化应该留下没有值的瞬态字段。 有没有办法告诉JSF这个会话范围的对象要求在反序列化后再次设置其依赖项?

我使用MyFaces JSF 1.2和Tomcat 6.0.26作为我的Web应用程序容器。

虽然Bozho提供的解决方案可以工作,但我不想将代理对象引入当前没有使用它们的应用程序中。 我的解决方案不太理想,但它完成了工作。

我把瞬态场留在了原地:

 transient private ApplicationData _applicationData; 

我还将setter留在原位,因此JSF可以在第一次创建SessionData对象时初始设置引用:

 public void setApplicationData(ApplicationData applicationData) { _applicationData = applicationData; } 

我做的改变是在getter方法中。 SessionData对象中的方法现在需要停止直接访问_applicationData字段,而是通过getter获取引用。 getter将首先检查null引用。 如果为null,则通过FacesContext获取托管bean。 这里的约束是FacesContext仅在请求的生命周期内可用。

 /** * Get a reference to the ApplicationData object * @return ApplicationData * @throws IllegalStateException May be thrown if this method is called * outside of a request and the ApplicationData object needs to be * obtained via the FacesContext */ private ApplicationData getApplicationData() { if (_applicationData == null) { _applicationData = JSFUtilities.getManagedBean( "applicationData", // name of managed bean ApplicationData.class); if (_applicationData == null) { throw new IllegalStateException( "Cannot get reference to ApplicationData object"); } } return _applicationData; } 

如果有人关心,这里是我的getManagedBean()方法的代码:

 /** * 

Retrieve a JSF managed bean instance by name. If the bean has * never been accessed before then it will likely be instantiated by * the JSF framework during the execution of this method.

* * @param managedBeanKey String containing the name of the managed bean * @param clazz Class object that corresponds to the managed bean type * @return T * @throws IllegalArgumentException Thrown when the supplied key does * not resolve to any managed bean or when a managed bean is found but * the object is not of type T */ public static T getManagedBean(String managedBeanKey, Class clazz) throws IllegalArgumentException { Validate.notNull(managedBeanKey); Validate.isTrue(!managedBeanKey.isEmpty()); Validate.notNull(clazz); FacesContext facesContext = FacesContext.getCurrentInstance(); if (facesContext == null) { return null; } Validate.notNull(facesContext.getApplication()); ELResolver resolver = facesContext.getApplication().getELResolver(); Validate.notNull(resolver); ELContext elContext = facesContext.getELContext(); Validate.notNull(elContext); Object managedBean = resolver.getValue( elContext, null, managedBeanKey); if (!elContext.isPropertyResolved()) { throw new IllegalArgumentException( "No managed bean found for key: " + managedBeanKey); } if (managedBean == null) { return null; } else { if (clazz.isInstance(managedBean)) { return clazz.cast(managedBean); } else { throw new IllegalArgumentException( "Managed bean is not of type [" + clazz.getName() + "] | Actual type is: [" + managedBean.getClass().getName()+ "]"); } } }

并且不要选择我的validation电话。 在完成开发之后我会把它们拿出来! 🙂

你可以添加一个方法:

 private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); applicationData = initializeApplicationData(); } 

initializeApplicationData您可以使用动态代理对象。 使用CGLIB或javassist创建一个代理,在每个方法调用之前设置一个内部字段 – 真正的ApplicationData 。 如果它为null ,则获取当前的FacesContext (在该点可访问)并从那里获取托管bean:

 FacesContext facesContext = FacesContext.getCurrentInstance(); originalApplicationData = (ApplicationData)facesContext.getApplication() .createValueBinding("#{applicationData}").getValue(facesContext); 

并委托其方法。

这是一个丑陋的解决方法,但我认为它会起作用。