【问题标题】:How to re-inject a transient @ManagedProperty at deserializing?如何在反序列化时重新注入瞬态 @ManagedProperty?
【发布时间】:2012-05-23 23:47:15
【问题描述】:

我正在使用 Spring 和 JSF 2 创建一个 Web 应用程序。 业务对象保存在 Spring 容器中,我使用 @ManagedProperty 将它们注入托管 Bean,如下所示:

@ManagedBean
@ViewScoped
public class SomeMB implements Serializable {
    private static final long serialVersionUID = 1L;

    @Getter @Setter
    @ManagedProperty("#{someService}")
    private SomeService someService;
    // ...

问题是,我不断收到来自 Spring (ServiceLocatorFactoryBean) 的类的 NotSerializableException,它正被 SomeService bean 使用。

如果我把它设为transient,我如何在反序列化后重新注入它?

或者,还有什么其他方法可以解决这个问题?

我一直在阅读其他几个类似的问题,但找不到任何与此问题完全相关的问题。

【问题讨论】:

  • 仅供参考:如果您只使用 Java EE 自己的 EJB 而不是 Spring,则不存在此问题。
  • @BalusC 是的,我在其他问题中读到过,不幸的是我对 EJB 的了解还不够多,无法使用它(而且我不知道我是否可以说服同事让我在这个项目中试试)。顺便说一句,你能给我指出一个很好的资源来了解它吗?
  • 这并不难。只需确保您的容器已经支持 EJB(Glassfish、JBoss、Weblogic 等)。使用@Stateless@Stateful 注释服务类并通过@EJB 注入它。而已。顺便说一句,不需要 getter/setter。
  • 我们只使用 Tomcat 7。:/ 但是谢谢,我很快会在个人项目上尝试一下。
  • 您可以在 Tomcat 7 上使用 OpenEJB 或将其替换为 TomEE:openejb.apache.org

标签: spring jsf-2 dependency-injection


【解决方案1】:

请记住 Spring 手册中的这一点 ( link to spring):

基于构造函数还是基于 setter 的 DI?

由于您可以混合使用基于构造函数和基于 Setter 的 DI,因此将构造函数参数用于强制依赖项并将 setter 用于可选依赖项是一个很好的经验法则。请注意,在 setter 上使用 @Required 注解可用于使 setter 成为必需的依赖项。

Spring 团队通常提倡 setter 注入,因为大量的构造函数参数会变得笨拙,尤其是当属性是可选的时。 Setter 方法还使该类的对象可以在以后重新配置或重新注入。通过 JMX MBean 进行管理是一个引人注目的用例。

一些纯粹主义者喜欢基于构造函数的注入。提供所有对象依赖意味着对象总是以完全初始化的状态返回给客户端(调用)代码。缺点是对象变得不太适合重新配置和重新注入。

使用对特定类最有意义的 DI。有时,在处理您没有源代码的第三方类时,会为您做出选择。遗留类可能不会公开任何 setter 方法,因此构造函数注入是唯一可用的 DI。

【讨论】:

  • 对不起,我没有关注...基于构造函数和基于 setter 的 DI 与我的问题有什么关系?
【解决方案2】:

不是通过@ManagedProperty 注释中的EL 注入Spring bean(在ManagedBean 初始化时执行),而是在运行时获取评估EL 的bean。

使用这种方法,JSF bean 应该是这样的:

@ManagedBean
@ViewScoped
public class SomeMB implements Serializable {
    private static final long serialVersionUID = 1L;

    private static SomeService someService() {
        return SpringJSFUtil.getBean("someService");
    }
    // ...

以及通过EL获取bean的实用类SpringJSFUtil.java

import javax.faces.context.FacesContext;

public class SpringJSFUtil {

    public static <T> T getBean(String beanName) {
        if (beanName == null) {
            return null;
        }
        return getValue("#{" + beanName + "}");
    }

    @SuppressWarnings("unchecked")
    private static <T> T getValue(String expression) {
        FacesContext context = FacesContext.getCurrentInstance();
        return (T) context.getApplication().evaluateExpressionGet(context,
                expression, Object.class);
    }
}

这消除了 Spring bean 属性(以多做几次 EL 评估为代价),从而避免了首先拥有该属性的所有序列化问题。

同样的方法,使用OmniFaces:

在我的实际代码中,我使用utility classevaluateExpressionGet(String expression) 方法,可从OmniFaces 获得。所以,对于那些也使用它的人来说,这就是我的代码的真实样子:

import static org.omnifaces.util.Faces.evaluateExpressionGet;

@ManagedBean
@ViewScoped
public class SomeMB implements Serializable {
    private static final long serialVersionUID = 1L;

    private static SomeService someService() {
        return evaluateExpressionGet("#{someService}");
    }
    // ...

请注意,这里的方法获取完整的 EL(“#{expression}”),而不仅仅是 Spring bean 名称(否则您会得到 ClassCastException)。

【讨论】:

    【解决方案3】:

    在 Spring @Service 上尝试 @Scope(value = BeanDefinition.SCOPE_SINGLETON, proxyMode = ScopedProxyMode.INTERFACES)。这应该将一个可序列化的代理对象注入到您的托管 bean 中,该对象将在反序列化后访问时重新定位服务。

    【讨论】:

      【解决方案4】:

      对于那些后续 - 我在注入 ResourceBundle 时遇到了类似的问题。使用 BalusC 的部分答案,我做了以下事情:

      @ManagedProperty(value="#{myBundle}")
      private transient ResourceBundle myBundle;
      
      private Object readResolve() {
          myBundle = FacesContext.getCurrentInstance().getApplication()
              .evaluateExpressionGet(FacesContext.getCurrentInstance(), "#{myBundle}",
              ResourceBundle.class);
          return this;
      }
      

      这样,只有在反序列化托管 bean 时才评估 EL。

      【讨论】:

        猜你喜欢
        • 2015-10-18
        • 2015-04-18
        • 1970-01-01
        • 2020-02-25
        • 2021-04-22
        • 1970-01-01
        • 2012-12-20
        • 2012-08-26
        • 2011-02-10
        相关资源
        最近更新 更多