多年来,我们-Java / JVM社区-对public static void main(...)
手工编写产生了恐惧。我们要么通过使用应用程序服务器完全摆脱了它,要么在使用像Guice或Spring这样的依赖注入框架时将其限制为残缺的形式。这是正确的方法吗?
反之。main()
按照字典的定义,该方法应该是或应该是“ 大小,范围或重要性的首长;主要; 领先于我们程序的“方法”(嗯,也许大小不对!:))。如果它是如此重要,它应该在我们的代码库中占据重要位置!不是我们经常拥有的存根(如果有的话),而是我们正在编写的系统的正确,精心设计的启动顺序。
应用程序服务器,DI容器和注释确实有助于提高我们编写软件的整体方法。但是,该继续前进了。我们的语言已经发展。我们不再受Java 1.5的束缚。现在,我们有了Java中的lambda,包括Scala,Kotlin,Ceylon和其他许多语言。我们将认识到函数式编程的所有好处,并学习如何将其与我们当前的开发实践最佳地融合在一起。
该main()
方法不仅是执行程序时运行时的主要入口。它也是读取代码的主要切入点(众所周知,使代码易于阅读比易于编写更为重要)。当我们想知道程序的功能时,这是最好的起点。它是否公开任何http端点?它是否连接到数据库?是否在服务注册表中注册?以什么顺序?这些问题可以通过写得很好快速而清楚地回答main()
。
事件监听器?
聆听野外事件
事件和事件侦听器(例如,应用程序启动事件)通常会代替main()
方法,但仅在某种程度上可以代替。通常,如果我们要进行一些初始化工作,则可以使用事件监听器。但是,尽管事件监听器确实非常有用,并且是一个非常好的解耦工具,但是它不能替代明确,清晰的启动顺序。表示需要遵循的步骤序列是编程时的基本结构之一,因此没有理由不使用它。
事件特别糟糕的一件事是保持适当的秩序。有一些变通办法,例如指定触发事件侦听器的顺序,但是绝对最好不要首先采用变通办法!
例如,如果我们首先尝试绑定到端口,然后在服务注册表中注册,或者相反,则存在显着差异。如果由于某种原因绑定失败,我们可能最终会在注册表中注册了无法运行的服务,或者-如果启动顺序已正确编码-避免这种情况。另一个很好的例子是启动缓存。通常,在服务可以开始为请求提供服务之前,需要第一次刷新缓存,然后才可以为http请求提供服务。
事件监听器的一个好用例是插入我们正在使用的第三方组件或库的生命周期;但是我们不应该将我们的应用程序视为第三方组件。
启动很重要
公开http端点,连接到数据库,启动缓存—这些都是系统所需的基本过程的示例。如何初始化组件,按照什么顺序初始化以及如何处理错误是系统内部工作的非常重要的方面。该main()
方法是使它们明确的一个很好的地方。
启动过程可能比您想象的重要。为什么隐藏它?
复苏之路
DIY接线
在我们消除对主要事物的恐惧的同时,也可能是停止恐惧的好时机new
。这是一个类似的故事:我们已经new
通过使用DI框架几乎消除了对的使用,DI框架为我们完成了所有对象-图的连接,有时还需要一组有用的注释。看起来很方便,而且一开始就是这样。但是,通过放弃对创建对象图的方式的控制,我们放弃了很多自由。随着代码库的增长和老化,我们对新项目进行快速引导,以确保代码库的确定性,可控制性和可探索性。
没有比该main()
方法更好的创建对象图的地方了!它也非常灵活-我们可以使用主机语言来创建单例,工厂,基于配置动态选择实现等。Java,Scala和Kotlin都是相当有表现力的语言。尽管乍一看似乎不太花哨,但重新获得对启动顺序和对象图创建的完全控制实际上是很自由的。尝试一下!
当然,在编写“常规”代码时遵循的所有最佳实践也都适用于该main()
方法。我们不应该让它变得肿且不可读,将其分为方法和类或引入抽象。它可能涉及多个方法和类:但是这里的区别在于,我们的系统仍然有一个明确定义的入口点,具有清晰的启动顺序。如果我们需要了解特定步骤的详细信息,可以在IDE中进行定义。
加起来
让我们回到main()
和new
在我们每天写代码其应有的作用!