简体   繁体   English

Spring依赖注入可序列化的bean

[英]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就是要在摆脱依赖StuffFactorySomeBean完全。 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 . 显然,您需要在SomeBeanfactory创建一个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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM