【问题标题】:JSF - Session-scoped managed bean does not have dependencies re-injected on session deserializationJSF - 会话范围的托管 bean 没有在会话反序列化时重新注入依赖项
【发布时间】:2011-04-16 06:06:48
【问题描述】:

我不确定我在做什么是错的,或者我只是在某处遗漏了注释或配置项。情况如下:

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

<managed-bean>
    <managed-bean-name>sessionData</managed-bean-name>
    <managed-bean-class>my.package.SessionData</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
    <managed-property>
        <property-name>applicationData</property-name>
        <property-class>my.package.ApplicationData</property-class>
        <value>#{applicationData}</value>
    </managed-property>
</managed-bean>
<managed-bean>
    <managed-bean-name>applicationData</managed-bean-name>
    <managed-bean-class>my.package.ApplicationData</managed-bean-class>
    <managed-bean-scope>application</managed-bean-scope>
</managed-bean>

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

transient private ApplicationData applicationData;

在 Web 应用程序停止(在我的 Tomcat 6.x 容器中)并且会话被序列化之前,一切都很好。当我重新启动应用程序并且会话被反序列化时,我对 ApplicationData 的引用不会被 JSF 重新注入。我知道反序列化应该使瞬态字段没有值。 有没有办法向 JSF 发出信号,表明此会话范围的对象需要在反序列化后再次设置其依赖项?

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

【问题讨论】:

  • 建议我提供一个 readObject() 方法,并在反序列化期间使用 FacesContext 在其中手动设置 ApplicationData 对象。我认为这不会起作用,因为 FacesContext 仅在请求的生命周期内可用。反序列化发生在应用程序启动时。
  • 正确,这就是我删除答案的原因。它看起来更复杂(因此问题+1)

标签: java jsf serialization dependency-injection httpsession


【解决方案1】:

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

我将瞬态字段留在原处:

transient private ApplicationData _applicationData;

我还保留了 setter,以便 JSF 可以在第一次创建 SessionData 对象时初始设置引用:

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

我所做的更改是在 getter 方法中。 SessionData 对象中的方法现在需要停止直接访问 _applicationData 字段,而是通过 getter 获取引用。 getter 将首先检查空引用。如果为 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() 方法的代码:

/**
 * <p>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.</p>
 * 
 * @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> T getManagedBean(String managedBeanKey, Class<T> 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()+
                "]");
        }
    }
}

不要接听我的验证电话。开发完成后我会把它们拿出来! :)

【讨论】:

【解决方案2】:

你可以添加一个方法:

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);

并委托给它的方法。

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

【讨论】:

  • 这将是我第一次使用 javassist,所以我需要一些时间来消化你解决方案的这一部分。谢谢。
  • 这很容易。只需按照教程进行操作 - 您的代理案例是最简单的 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-04-24
  • 2012-12-26
  • 2012-06-27
  • 2015-08-22
  • 2014-06-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多