[英]Spring Dependency Injection into serializable beans
I have a service class which is not serializable and a bean which must be serializable but must have access to this service class: 我有一个不可序列化的服务类和一个必须可序列化但必须有权访问此服务类的bean:
class SomeBean implements Serializable
{
private StuffFactory factory;
@Autowired
public SomeBean(StuffFactory factory)
{
this.factory = factory;
}
public getOther()
{
return this.factory.getSomeOtherStuff();
}
}
This obviously doesn't work because now the SomeBean
class is no longer serializable. 这显然不起作用,因为现在
SomeBean
类不再可序列化。 What is the correct way to solve this in Spring? 在Spring中解决这个问题的正确方法是什么? When I make the
factory
field transient then I loose the injected factory instance on deserialization, or not? 当我将
factory
现场设置为瞬态时,我是否在反序列化时松开了注入的工厂实例? And when I make the StuffFactory
also serializable then this class will no longer be a singleton because each SomeBean
instance will have it's own factory after deserialization then. 当我使
StuffFactory
也可序列化时,这个类将不再是一个单例,因为每个SomeBean
实例在反序列化之后将拥有它自己的工厂。
You need some kind of context for this magic to work. 你需要某种背景才能使这种魔法发挥作用。
One ugly way I can think of is a static ThreadLocal
holding the ApplicationContext
- it's how the spring-security works using SecurityContextHolder
. 我能想到的一个丑陋的方法是持有
ApplicationContext
的静态ThreadLocal
- 它是Spring-security如何使用SecurityContextHolder
。
But the best if You'd be able to externalize the logic that requires the StuffFactory
to some kind of singleton SomeBeanService
, ie: 但最好的情况是,你能够将需要
StuffFactory
的逻辑外部StuffFactory
某种单例SomeBeanService
,即:
public class SomeBeanService {
@Autowired
private StuffFactory stuffFactory;
public void doWithSomeBean(SomeBean bean) {
// do the stuff using stuffFactory here
}
}
Update: 更新:
The whole point in above alternative to ThreadLocal
is to get rid of the dependency on StuffFactory
from SomeBean
completely. 在上述替代整点
ThreadLocal
就是要在摆脱依赖StuffFactory
从SomeBean
完全。 This should be possible, but will require changes in architecture. 这应该是可能的,但需要改变架构。 The separation of concerns (one of, not only Spring, basic rules) implies that it might be a good idea to let
SomeBean
be a simple data transfer object and the business logic to be moved to service layer. 关注点的分离 (不仅是Spring,基本规则之一)意味着让
SomeBean
成为一个简单的数据传输对象并将业务逻辑移动到服务层可能是个好主意。
If You'll be unable to achieve this, than the only way is using some kind of static
context (as Ralph also said). 如果你无法实现这一点,那么唯一的方法是使用某种
static
上下文(正如拉尔夫所说)。 The implementation of such context may involve using a ThreadLocal
. 这种上下文的实现可能涉及使用
ThreadLocal
。 This would allow to access the ApplicationContext
to get the StuffFactory
, but it's almost as ugly as global variables, so avoid it if possible. 这将允许访问
ApplicationContext
以获取StuffFactory
,但它几乎和全局变量一样难看,所以尽可能避免使用它。
Update2: UPDATE2:
I just saw Your comment, that SomeBean
is stored in HTTP session, and hence the serialization/deserialization issue. 我刚看到你的评论,
SomeBean
存储在HTTP会话中,因此序列化/反序列化问题。 Now even more I advice to change Your design and remove the dependency. 现在我建议更改你的设计并删除依赖。 Make
SomeBean
a simple DTO, as small as possible to avoid overloaded sessions. 使
SomeBean
成为一个简单的DTO,尽可能小以避免重载会话。 There should be no logic requiring access to singleton Spring beans inside the SomeBean
. 应该没有逻辑要求访问
SomeBean
单例Spring bean。 Such logic should be placed in the controller or service layer. 这种逻辑应该放在控制器或服务层中。
Maybe have a SomeBeanFactory
: 也许有一个
SomeBeanFactory
:
class SomeBeanFactory {
@Autowired
private StuffFactory stuffFactory;
public SomeBean deserialize(...) {
SomeBean someBean = ...;
someBean.setStuffFactory(stuffFactory);
return someBean;
}
}
Obviously, you'll need to create a setter for factory
in SomeBean
. 显然,您需要在
SomeBean
为factory
创建一个setter。
Java provide a way to control the serialization and deserialization by implementing the two methods: Java提供了一种通过实现这两种方法来控制序列化和反序列化的方法:
private void writeObject(ObjectOutputStream out) throws IOException;
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;
So you can change the write to store the bean without the reference to the service and later one while reading the object "inject" the reference again. 因此,您可以更改写入以存储bean而不引用服务,然后在读取对象时再次“注入”引用。
@see http://java.sun.com/developer/technicalArticles/Programming/serialization/ @see http://java.sun.com/developer/technicalArticles/Programming/serialization/
You MUST create the instance in readObject
as bean. 您必须在
readObject
创建实例作为bean。 If you only use a simple new
it will not become a bean. 如果你只使用一个简单的
new
东西,它将不会成为一个bean。 Therefore you need access to the Spring Context, for example via a static method. 因此,您需要访问Spring Context,例如通过静态方法。 May the best way is to use a bean factory to create the new instance.
可能最好的方法是使用bean工厂来创建新实例。
An other way you can try to make the instance a spring bean is using @Configurable
but that requires real AspectJ. 另一种可以尝试使实例成为spring bean的方法是使用
@Configurable
但这需要真正的AspectJ。
A re-think of your architecture is potentially practical in this scenario. 在这种情况下,重新考虑您的体系结构是可行的。 If you can isolate the data that needs to be serialized into a simple data transfer object (DTO), then it should be possible to provide a wrapper to that DTO that itself can contain dependencies to other beans.
如果您可以将需要序列化的数据隔离到一个简单的数据传输对象(DTO)中,那么应该可以为该DTO提供一个包装器,它本身可以包含对其他bean的依赖关系。
For example: 例如:
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);
}
}
Sorry for all the code! 对不起,所有的代码! However, let me try to explain what I mean here.
但是,让我试着解释一下我的意思。 If you always retrieve the session bean via a service (in this case SomeService), you have the option of creating a wrapper around the session bean.
如果总是通过服务检索会话bean(在本例中为SomeService),则可以选择在会话bean周围创建包装器。 This wrapper can contain any bean dependencies (autowired into SomeService) that you may wish to use as part of performing logic alongside the session bean.
这个包装器可以包含任何bean依赖项(自动连接到SomeService),您可能希望将它们用作在会话bean旁边执行逻辑的一部分。
The neat thing about this approach is that you can program to an interface as well (see IDataBean). 这种方法的巧妙之处在于您也可以编程到接口(请参阅IDataBean)。 This means that, if you, for example, had a controller that obtains the data bean from the service, it makes unit testing/mocking very clean.
这意味着,例如,如果您有一个从服务获取数据bean的控制器,它会使单元测试/模拟非常干净。
Possibly, an even cleaner approach from the code perspective, would be for MyDataBeanWithDependency to be registered in the spring container using "request" scope. 可能,从代码角度来看,更简洁的方法是使用“请求”范围在Spring容器中注册MyDataBeanWithDependency。 So you could just autowire that bean directly into SomeService.
所以你可以直接将该bean自动装入SomeService。 This would essentially transparently deal with the instantiation so you don't need to manually instantiate MyDataBeanWithDependency from within the service.
这基本上是透明地处理实例化,因此您不需要从服务中手动实例化MyDataBeanWithDependency。
Hopefully I've done enough to explain myself here! 希望我已经做得足以在这里解释自己!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.