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);
并委托其方法。
这是一个丑陋的解决方法,但我认为它会起作用。