【问题标题】:Spring Dependency Injection into serializable beansSpring依赖注入到可序列化的bean中
【发布时间】:2011-10-14 05:58:15
【问题描述】:

我有一个不可序列化的服务类和一个必须可序列化但必须有权访问此服务类的 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 实例在反序列化后都会有自己的工厂。

【问题讨论】:

    标签: java spring serialization dependency-injection


    【解决方案1】:

    你需要某种上下文来让这个魔法发挥作用。

    我能想到的一种丑陋方式是静态的ThreadLocal 持有ApplicationContext - 这就是使用SecurityContextHolder 的spring-security 的工作方式。

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

    public class SomeBeanService {
        @Autowired
        private StuffFactory stuffFactory;
    
        public void doWithSomeBean(SomeBean bean) {
            // do the stuff using stuffFactory here
        }
    }
    

    更新:

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

    如果您无法实现这一点,那么唯一的方法是使用某种static 上下文(正如 Ralph 所说)。这种上下文的实现可能涉及使用ThreadLocal。这将允许访问ApplicationContext 以获取StuffFactory,但它几乎与全局变量一样丑陋,因此请尽可能避免使用它。

    更新2:

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

    【讨论】:

    • 但是我必须注入这个 SomeBeanService 代替。或者你的意思是在没有弹簧的情况下访问它?这有点类似于非托管 bean 通常使用的服务委托者模式。但是我的SomeBean 是由弹簧管理的,所以我希望 Spring 在处理瞬态依赖方面有一些魔力。好吧,如果没有更好的解决方案弹出我想我必须接受你的回答。
    【解决方案2】:

    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 上下文,例如通过静态方法。 最好的办法是使用bean factory来创建新的实例。


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

    【讨论】:

    • 但是如何在readObject 方法中获取stuffFactory 实例来重新初始化依赖?这个依赖在创建 bean 时由 Spring 自动注入。我在 readObject 方法中没有可用的应用程序上下文来从那里手动获取 bean。我希望有一些 Spring 魔法来处理这个问题。
    • @kayahr:查看我的扩展答案
    • 但是 Spring bean 通常根本不知道 Spring,因此通过静态方法获取应用程序上下文并不是一个好主意。当我这样做时,我可以简单地放弃整个依赖注入想法并通过静态getInstance 方法或类似的方法访问StuffFactory
    • 抱歉,存储一个 bean 根本不是一个好主意,所以你需要做一些妥协。 -- 第二:“Spring bean 通常根本不知道 Spring”这句话可能适用于业务 bean,但不适用于基础设施组件。您在这里所做的是将基础设施混合到一个正常的业务 bean 中。 -- 无论如何,您可以通过使用@Configurable 注释类来掩盖这种强烈的弹簧依赖/访问
    • @Ralph:对于@Configured,加载时间编织就足够了吗?还是需要编译时编织? SomeBean 编织时是否仍可序列化?
    【解决方案3】:

    也许有一个SomeBeanFactory

    class SomeBeanFactory {
        @Autowired
        private StuffFactory stuffFactory;
    
        public SomeBean deserialize(...) {
            SomeBean someBean = ...;
            someBean.setStuffFactory(stuffFactory);
            return someBean;
        }
    }
    

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

    【讨论】:

    • 序列化和反序列化由 Web 容器完成(SomeBean 实例位于 HTTP 会话中的某处),因此我无法将反序列化委托给工厂。
    【解决方案4】:

    在这种情况下,重新考虑您的架构可能是可行的。如果您可以将需要序列化的数据隔离到一个简单的数据传输对象 (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); 
        }
    }
    

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

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

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

    希望我已经做了足够的工作来在这里解释自己!

    【讨论】:

      猜你喜欢
      • 2012-08-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-14
      • 1970-01-01
      • 2013-07-08
      • 2014-06-28
      • 1970-01-01
      相关资源
      最近更新 更多