Java 在IoC容器中将依赖项设置为NULL并在运行时提供依赖项是否是一种不好的做法?
我有一个Java 在IoC容器中将依赖项设置为NULL并在运行时提供依赖项是否是一种不好的做法?,java,design-patterns,dependency-injection,abstract-factory,Java,Design Patterns,Dependency Injection,Abstract Factory,我有一个SocketManager类,它包含一个Socket和其他字段。在使用DI框架合成对象图的过程中,除了套接字之外的所有字段都可以被注入。我的想法是将Socket保留为空,并在运行时设置它,从而预先构建整个对象图。这将允许我在代码中的某个点完成SocketManager实例化,并在整个程序中使用该实例(因为它已经通过DI框架设置为依赖项)?这是“注入”运行时依赖项的标准方法还是不好的做法? 抽象工厂似乎是个坏主意,原因有两个:A)它每次都创建一个不同的对象b)它需要在我想要创建对象的每个地
SocketManager
类,它包含一个Socket
和其他字段。在使用DI框架合成对象图的过程中,除了套接字
之外的所有字段都可以被注入。我的想法是将Socket
保留为空,并在运行时设置它,从而预先构建整个对象图。这将允许我在代码中的某个点完成SocketManager
实例化,并在整个程序中使用该实例(因为它已经通过DI框架设置为依赖项)?这是“注入”运行时依赖项的标准方法还是不好的做法?抽象工厂似乎是个坏主意,原因有两个:A)它每次都创建一个不同的对象b)它需要在我想要创建对象的每个地方使用运行时参数 让我举例说明我的问题: SocketManager类:
public class SocketManager {
//i'll only receive the socket at runtime
Socket socket;
//this object is available at compile-time and can be injected through the DI container
InjectableObject obj;
}
在我的代码[CodePosition1]的某个地方,我将收到如下套接字:
public class SocketCreator{
SocketManager socketManager; //will be injected through DI container at startup
Socket socket = this.serverSocket.accept();
// at this point the socket manager is fully initialized
socketManager.setSocket(socket);
}
在许多其他地方[CodePosition2],我现在可以使用SocketManager依赖项
public class RandomClass {
//injected at compile-time through DI container, but only usable after [CodePosition1]
// was executed
SocketManager socketManager;
...
socketManager.getSocket().doSth()
...
}
问题是,
SocketManager
在运行时[CodePosition1]之前没有完全初始化,所以除了在SocketManager上使用init()或setter来“完成”SocketManager的初始化之外,我不知道其他方法。然而,这是一个漏洞百出的抽象,正如本文所解释的:最好从一开始就编写完整的对象图。应该防止注入null
值,因为这会使消费类复杂化
然而,在您的例子中,Socket
似乎不是一个“真正的”组件,而是运行时数据。如上所述,您应该防止在构建期间将运行时数据注入到对象图中
那篇文章给出了两种解决这个问题的方法,但是还有更多的方法。然而,正如您已经提到的,抽象工厂通常不是一个好的解决方案,并且在更一般的意义上描述了抽象工厂的问题所在。第段甚至从DI的角度详细介绍了抽象工厂的问题
博客文章中给出的解决方案是使用“上下文”抽象。例如,在您的例子中,一个SocketContext
接口,它允许您在调用消费者的方法之后,以及在构建消费者的对象图之后,由消费者获取Socket
运行时值。例如:
public interface SocketContext
{
Socket get_CurrentSocket();
}
public class SocketManagerLazyProxy : SocketManager
{
private SocketManager mananger;
public void DoSomething()
{
if (manager == null) manager = new RealSocketManager(new Socket());
manager.DoSomething();
}
}
另一个选项是使用一个代理类,该类隐藏realSocket
或realSocketManager
(取决于可以放置代理的级别)。这允许使用者不知道某些运行时数据需要在后台初始化,并且在第一次调用后可能会延迟初始化。例如:
public interface SocketContext
{
Socket get_CurrentSocket();
}
public class SocketManagerLazyProxy : SocketManager
{
private SocketManager mananger;
public void DoSomething()
{
if (manager == null) manager = new RealSocketManager(new Socket());
manager.DoSomething();
}
}
另一个选项是在构建对象图后使用属性注入来设置套接字
值。这允许您更早地构建对象图,并在请求传入时设置运行时值,方法是在请求传入时设置该值:
void HandleRequest(RequestData data)
{
SocketManager manager = GetSocketManagerForThisRequest();
manager.Socket = new Socket();
Handler handler = GetHandler(data.Name);
handler.Handle(data);
}
你能再解释一下吗?也许可以添加一些代码。。。您打算使用什么DI框架?@FabioBohnenberger我添加了一个示例来说明我的观点;-)如果客户端代码在套接字可用之前与
SocketManager
交互,会发生什么情况?是否有多个并发套接字?插座能再次消失吗?不,只有一个SocketManager和一个插座,插座不能消失。我在没有任何DI框架的情况下启动了这个项目,因此,Socket
总是在程序的早期创建的。然后我通过构造函数将SocketManager
传递到整个堆栈的任何需要的地方,这显然是不好的。因此,在实际操作中,RandomClass
使用SocketManager时,它将被完全初始化。如果我可以问的话,您的问题与我的演示问题有什么关系?我得出的结论是,在运行时设置的变量在某一点上肯定是空的(无论您是否将其包装到容器中)。这没有意义。因此,在消费者调用该变量之前,程序员的责任是确保首先设置该变量。在使用DI框架时,这是您必须接受的权衡,因为一个普通的构造函数链将迫使您指定一个时间链,这保证了一个“之前发生”的关系。感谢Steven,我添加了一个示例来说明我的观点。让我们来看一下“SocketContext”解决方案。据我所知,SocketContext与SocketManager类似,因此同样的问题仍然存在:如何在运行时设置SocketContext
?您需要将Socket
保留为NULL,并在运行时完成SocketContext的初始化。只有这样,您才能使用get\u CurrentSocket()
。在没有init()或setter的情况下,如何解决这个问题,如示例中所示?在我看来,依赖项的方向是错误的。如果让SocketManager
依赖于SocketCreator
,它可以在第一次请求Socket
时调用创建者。如果SocketManager
具有SocketCreator
依赖性,那么我可以通过SocketCreator
使用套接字,这就是你的意思吗?但是,当我通过SocketManager调用SocketCreator时,如何确保它已实际设置?这不是某种时间耦合问题吗?此外,在对象图初始化期间,Socket
内部的SocketCreator
需要成为类成员并在编译时为NULL。为什么必须设置creator?创建不是创建者的工作吗?嗯,我想我们应该确保在访问套接字之前设置好它,否则SocketManager
将处于