Spring依赖注入可序列化的bean

时间:2011-10-14 05:58:15

标签: java spring serialization dependency-injection

我有一个不可序列化的服务类和一个必须可序列化但必须能够访问此服务类的bean:

class SomeBean implements Serializable
{
    private StuffFactory factory;

    @Autowired
    public SomeBean(StuffFactory factory)
    {
        this.factory = factory;
    }

    public getOther()
    {
        return this.factory.getSomeOtherStuff();
    }
}

这显然不起作用,因为现在SomeBean类不再可序列化。在Spring中解决这个问题的正确方法是什么?当我使factory字段瞬态时,我在反序列化时松开注入的工厂实例,或不?当我使StuffFactory也可序列化时,这个类将不再是单例,因为每个SomeBean实例在反序列化之后将拥有它自己的工厂。

4 个答案:

答案 0 :(得分:2)

您需要某种上下文才能使 magic 正常工作。

我能想到的一个丑陋的方法是持有ThreadLocal的静态ApplicationContext - 这就是使用SecurityContextHolder进行弹簧安全的方式。

但是,如果你能够将需要StuffFactory的逻辑外部化为某种单身SomeBeanService,那就最好了,即:

public class SomeBeanService {
    @Autowired
    private StuffFactory stuffFactory;

    public void doWithSomeBean(SomeBean bean) {
        // do the stuff using stuffFactory here
    }
}

<强>更新

上面替代ThreadLocal的重点是完全摆脱StuffFactorySomeBean的依赖。这应该是可能的,但需要改变架构。 关注点分离(不仅是Spring,基本规则之一)意味着让SomeBean成为简单的数据传输对象可能是个好主意以及要移动到服务层的业务逻辑。

如果你无法实现这一目标,那么唯一的方法是使用某种static语境(如拉尔夫所说)。这种上下文的实现可能涉及使用ThreadLocal。这样就可以访问ApplicationContext来获取StuffFactory,但它几乎和全局变量一样难看,所以尽可能避免使用它。

<强> UPDATE2:

我刚刚看到您的评论,SomeBean存储在HTTP会话中,因此存在序列化/反序列化问题。现在我建议更改你的设计并删除依赖。使SomeBean成为一个简单的DTO,尽可能小,以避免重载会话。应该没有逻辑要求访问SomeBean内的单例Spring bean。这种逻辑应该放在控制器或服务层中。

答案 1 :(得分:0)

也许有SomeBeanFactory

class SomeBeanFactory {
    @Autowired
    private StuffFactory stuffFactory;

    public SomeBean deserialize(...) {
        SomeBean someBean = ...;
        someBean.setStuffFactory(stuffFactory);
        return someBean;
    }
}

显然,您需要在factory中为SomeBean创建一个setter。

答案 2 :(得分:0)

Java提供了一种通过实现这两种方法来控制序列化和反序列化的方法:

  • private void writeObject(ObjectOutputStream out) throws IOException;
  • private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;

因此,您可以更改写入以存储bean而不引用服务,稍后在读取对象时再次“注入”引用。

@see http://java.sun.com/developer/technicalArticles/Programming/serialization/

必须readObject中创建实例作为bean。如果你只使用一个简单的new它就不会成为一个bean。 因此,您需要访问Spring Context,例如通过静态方法。 可能最好的方法是使用bean工厂来创建新实例。


您可以尝试将实例设为spring bean的另一种方法是使用@Configurable,但这需要真正的AspectJ。

答案 3 :(得分:0)

在这种情况下,重新考虑您的体系结构是可行的。如果您可以将需要序列化的数据隔离到一个简单的数据传输对象(DTO)中,那么应该可以为该DTO提供一个包装器,它本身可以包含对其他bean的依赖。

例如:

public interface IDataBean {
    void setSomething(String someData);
}

// This is your session bean - just a plain DTO
public class MyDataBean implements IDataBean, Serializable {

   private String someData;

   public void setSomething( String someData ) {
      this.someData = someData;
   }
}

// This is the wrapper that delegates calls to a wrapped MyDataBean.
public class MyDataBeanWithDependency() implements IDataBean {

    private SomeOtherService service;

    private MyDataBean dataBean;

    public SimpleDataBeanWithDependency(MyDataBean dataBean, SomeOtherService service) {
       this.dataBean = dataBean;
       this.service = service;
    }

    public void setSomething(String someData) {
       // Here we make a call to the service to perform some specific logic that may, for example, hit a DB or something.
       String transformedString = service.transformString(someData);
       dataBean.setSomething(transformedString);
    }
}

public class SomeService {

    // This is a Spring session scoped bean (configured using <aop:scoped-proxy/>)
    @Autowired
    private MyDataBean myDataBean;

    // Just a plain singleton Spring bean
    @Autowired
    private SomeOtherService someOtherService;

    public IDataBean getDataBean() {
         return new MyDataBeanWithDependency(myDataBean, someOtherService); 
    }
}

对不起所有代码!但是,让我试着解释一下我的意思。如果总是通过服务检索会话bean(在本例中为SomeService),则可以选择在会话bean周围创建包装器。这个包装器可以包含任何bean依赖项(自动连接到SomeService),您可能希望将它们用作在会话bean旁边执行逻辑的一部分。

这种方法的巧妙之处在于您也可以编程到接口(请参阅IDataBean)。这意味着,例如,如果您有一个从服务获取数据bean的控制器,它会使单元测试/模拟非常干净。

从代码的角度来看,更简洁的方法可能是使用“请求”范围在Spring容器中注册MyDataBeanWithDependency。所以你可以直接将该bean自动装入SomeService。这基本上是透明地处理实例化,因此您不需要从服务中手动实例化MyDataBeanWithDependency。

希望我已经做得足以在这里解释自己了!