我有一个不可序列化的服务类和一个必须可序列化但必须能够访问此服务类的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
实例在反序列化之后将拥有它自己的工厂。
答案 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
的重点是完全摆脱StuffFactory
对SomeBean
的依赖。这应该是可能的,但需要改变架构。 关注点分离(不仅是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。
希望我已经做得足以在这里解释自己了!