第 14 章 模块化的未来

Chapter 14. A Modular Future

We’re nearing the end of our journey through the Java module system—starting from the modular JDK, all the way through creating your own modules and migrating existing code to modules. On the one hand, there’s a lot of new functionality around modules to take in. On the other hand, Java modules simply encourage tried-and-true best practices around modular development.

到目前为止,即将结束 Java 模块系统的学习之旅——首先从模块化 JDK 开始,然后创建自己的模块并将现有代码迁移到模块。一方面,需要弄清楚围绕模块的许多新的功能。另一方面,Java 模块鼓励在模块化开发中采用经过实践检验切实可行的最佳实践。

Using modules for new code is recommended except for the smallest of applications. Applying strong encapsulation and managing explicit dependencies from the start lays a solid foundation for maintainable systems. It also unlocks new capabilities such as creating a custom runtime image with jlink.

除了最小的应用程序之外,建议针对新代码采用模块。如果在一开始就应用强封装并管理显式依赖关系,那么就为创建可维护系统奠定了坚实的基础。同时还开启了新功能,例如使用 jlink 创建自定义运行时映像。

Entering the modular future of Java requires adherence to the principles of modularity. When your existing applications have a modular design, the transition will be smooth. Many people already use multimodule Maven or Gradle builds for their applications. Often these existing module boundaries map naturally to Java modules. In some cases, the ServiceLoader mechanism offered by the JDK is a good alternative to using a full-blown dependency injection framework.

进入 Java 的模块化未来需要遵守模块化原则。如果现有的应用程序采用了模块化设计,那么转换将会非常顺利。许多人已经使用多模块 Maven 或 Gradle 构建自己的应用程序。通常这些现有的模块边界可以自然映射到 Java 模块。在某些情况下,JDK 提供的 ServiceLoader 机制是成熟的依赖注入框架的一个很好的替代方法。

However, when your applications are anything but modular, the transition will be challenging.

但是,当应用程序不是模块化时,转换将是极具挑战性的。

第 14 章 模块化的未来 - 图1

You saw in earlier chapters that it is certainly possible to migrate existing applications to modules. When their design is lacking in modularity, though, you effectively take on two problems at the same time during migration. The first is untangling the architecture so that it conforms to the principles of modularity. The second is the actual migration to Java 9 and its module system.

从前面的章节中已经看到,将现有的应用程序迁移到模块是可能的。但是,当它们的设计缺乏模块化时,在迁移过程中需要有效地解决两个问题。首先是解决架构问题,使其符合模块化原则。其次是实际迁移到 Java 9 及其模块系统。

Whether it is worth to take on this effort is a trade-off that cannot be captured in a general guideline. It depends on the scope of the system, expected lifetime, and many other context-dependent variables. Note that even the massive JDK succeeded in modularizing itself, although that took years of hard work. Clearly, where there’s a will, there’s a way. Whether the benefits outweigh the costs is the main question to be answered for existing codebases. There is no shame in leaving an application on the classpath.

是否值得花费精力实现模块化在一般指导方针中并没有给出很好的权衡分析,这取决于系统的应用范围、预期的生命周期以及许多其他依赖于上下文的可变因素。请注意,大量的 JDK 都成功地进行了模块化,虽然这需要花费数年的努力。显然,有志者事竟成。所获得的收益是否超过成本是针对现有代码库需要回答的主要问题。在类路径上保留一个应用程序并不是一件丢脸的事情。

In the remainder of this chapter, we take stock of the Java module system in the existing landscape of modular development approaches.

本章的其余部分将总结现有的模块化开发方法中的 Java 模块系统。

14.1 OSGi

Long before the Java module system, other module systems emerged. These existing systems offer application-level modularity only, whereas the Java module system also modularizes the platform itself. The oldest and most well-known existing module system is OSGi. It offers run-time modularity by running bundles (JARs with OSGi wiring metadata) in an OSGi container. Isolation between bundles is achieved by controlling visibility of classes through a clever arrangement of classloaders. Essentially, each bundle is loaded by its own, isolated classloader, delegating only to other classloaders based on the bundle’s metadata. Isolation through classloaders happens only at run-time inside the OSGi container. At build-time, development tools such as Bndtools or Eclipse PDE must be used to enforce strong encapsulation and dependencies.

早在 Java 模块系统之前,其他模块系统就已经出现了。这些现有的系统只提供了应用程序级的模块化,而 Java 模块系统对平台本身进行了模块化。目前最古老且最知名的模块系统是 OSGi。它通过在 OSGi 容器中运行 bundle(具有 OSGi 布线元数据的 JAR)来提供运行时模块化。通过巧妙地安排类加载器来控制类的可见性,从而实现了 bundle 之间的隔离。实际上,每个 bundle 都由独立的类加载器加载,并根据 bundle 的元数据委托给其他类加载器。在 OSGi 容器中通过类加载器实现的隔离仅在运行时发生。而在构建时,必须使用诸如 Bndtools 或 Eclipse PDE 之类的开发工具来执行强封装和依赖关系。

Does OSGi become obsolete with the introduction of the Java module system? Far from it. First of all, existing OSGi applications can keep running on Java 9 by using the classpath. When you have an OSGi-based system, there’s no rush to make your bundles into Java modules. Also, the OSGi Alliance is doing preliminary work on interoperability between OSGi bundles and Java modules.

随着 Java 模块系统的引入,OSGi 过时了吗?事实并非如此。首先,现有的 OSGi 应用程序可以在 Java 9 上通过使用类路径继续运行。当有一个基于 OSGi 的系统时,不要急于将 bundle 转变为 Java 模块。另外,OSGi Alliance 正在进行 OSGi bundle 和 Java 模块之间互操作性的前期研究工作。

The question then becomes: when to use OSGi and when to use the Java module system for new systems? To answer that question, it’s important to understand how OSGi and the Java module system differ.

现在,问题就变成了:针对新的系统,何时使用 OSGi 以及何时使用 Java 模块系统?要回答这个问题,了解 OSGi 和 Java 模块系统之间的差异很重要。

There are some notable differences between OSGi and the Java module system:

OSGi 和 Java 模块系统之间存在以下显著差异:

  • Package dependencies 包依赖关系
    • OSGi bundles express dependencies on packages, not directly on other bundles (though this is possible as well). The OSGi resolver wires together bundles based on the exported and imported packages given in the bundle metadata. This makes a bundle’s name less important than a Java module name. Bundles export at the package level, just like Java modules.
    • OSGi bundle 表示了对包的依赖关系,而不是对其他 bundle 的依赖关系(尽管这也是可能的)。OSGi 解析器根据 bundle 元数据中给出的导出和导入包将 bundle 连接在一起。这使得 bundle 的名称比 Java 模块的名称更重要。与 Java 模块一样,在包级别导出 bundle。

  • Versioning 版本控制
    • Unlike modules in Java, OSGi has versions for both bundles and packages. Dependencies can be expressed on exact versions or version ranges. Because each bundle is loaded in a separate classloader, multiple versions of a bundle can coexist, although this is not without caveats.
    • 与 Java 中的模块不同,OSGi 中 bundle 和包都有版本。依赖项可以用精确的版本或版本范围来表示。因为每个 bundle 都被加载到一个单独的类加载器中,所以 bundle 的多个版本可以共存,但也不是没有限制。

  • Dynamic loading 动态加载
    • Bundles can be loaded, unloaded, started, and stopped inside an OSGi runtime. There are callbacks for these bundle life-cycle events, since bundles must cope with this dynamic environment. Java modules can be loaded at run-time in ModuleLayers, and be garbage collected later. In contrast with OSGi bundles, no explicit life-cycle callbacks are defined for Java modules, because a more static configuration is assumed.
    • bundle 可以在 OSGi 运行时中加载、卸载、启动和停止。这些 bundle 生命周期事件都有回调,因为 bundle 必须应对动态环境。可以在运行时在 ModuleLayer 中加载 Java 模块,并在稍后进行垃圾回收。与 OSGi bundle 相比,Java 模块没有定义明确的生命周期回调,因为它假设了更静态的配置。

  • Dynamic services 动态服务
    • OSGi also defines a services mechanism with a central service registry. The API for OSGi services is richer than what is offered by Java’s ServiceLoader. High-level frameworks (such as Declarative Services) are offered on top of basic OSGi services. OSGi services can come and go at run-time, not in the least because bundles offering them can come and go dynamically. Java services with ServiceLoader are wired once during module resolution. Only with ModuleLayer can new services be introduced at run-time. Unlike OSGi services, they do not support start and stop callbacks.
    • OSGi 还定义了一个带有中央服务注册表的服务机制。OSGi 服务的 API 比 Java 的 ServiceLoader 提供了更丰富的功能。许多高级框架(比如 Declarative Services)是在基本的 OSGi 服务之上提供的。OSGi 服务可以在运行时进出,而不是一成不变,因为提供服务的 bundle 可以动态地来回移动。带有 ServiceLoader 的 Java 服务仅在模块解析期间连线一次。只有使用 ModuleLayer 才能在运行时引入新的服务。与 OSGi 服务不同,Java 服务不支持启动和停止回调。

A repeating theme across these differences is that OSGi supports more-dynamic scenarios in its runtime. OSGi has more features in this regard than the Java module system. This is partly because OSGi’s roots are in embedded systems. Zero downtime updates are possible because OSGi bundles can be hot-swapped. New hardware can be plugged in, and services supporting them can be dynamically started.

除了上述差异之外,还有一个更重要的区别:OSGi 在运行时支持更多动态的场景。OSGi 在这方面比 Java 模块系统具有更多的功能,这是因为 OSGi 植根于嵌入式系统。零停机更新是可能的,因为 OSGi bundle 可以热插拔。一旦插入新的硬件,支持它们的服务就会动态启动。

In enterprise software, this same paradigm can extend to other resources with dynamic availability at run-time. If you need these dynamics, OSGi is the way to go. OSGi’s dynamic life cycle does add some complexity for developers. Part of this inherent complexity can be abstracted away during development by using higher-level frameworks such as Declarative Services. In practice, many applications (including those using OSGi) tend to wire together services at startup, without any dynamic changes afterward. In those cases, the Java module system offers enough functionality out of the box.

在企业软件中,同样的范例可以在运行时以动态可用性扩展到其他资源。如果需要这些动态性,就应该使用 OSGi。OSGi 的动态生命周期确实为开发人员添加了一些复杂性。部分固有的复杂性可以在开发过程中通过使用诸如 Declarative Services 之类的高级框架进行抽象化。实际上,许多应用程序(包括那些使用 OSGi 的应用程序)往往在启动时将服务连接在一起,之后没有任何动态变化。在这些情况下,Java 模块系统提供了足够的功能。

Tooling around OSGi has been a source of frustration to many developers. It just doesn’t have the critical mass behind it to get full attention of the community and vendors, even after more than a decade. With modules being part of the Java platform, vendors are already creating tooling and support ahead of its release. Because the OSGi framework is in action only at run-time, tooling has to mimic its rules during development. Java’s module system rules are enforced at all stages in a consistent manner, from development to run-time.

围绕 OSGi 的相关工具已经成为许多开发人员受挫的源头。即使十多年后,它也不具备足够的重要性来得到社区和供应商的充分关注。随着模块成为 Java 平台的一部分,供应商已经在其发布之前创建了工具和支持。由于 OSGi 框架仅在运行时才起作用,工具必须在开发过程中模仿其规则,而 Java 的模块系统规则在所有阶段(从开发到运行时)以一致的方式执行。

The Java module system also has features that are not in OSGi, mostly around migration to modules. There’s no direct OSGi equivalent of automatic modules. Not all Java libraries offer (correct) OSGi metadata, meaning patches or pull requests are the only means of using those libraries with OSGi. Libraries sometimes also contain code that doesn’t play nice with OSGi’s isolated classloading setup. In the Java module system, classloading is implemented in a more backward-compatible manner. Because the Java module system doesn’t use classloaders for isolation, it offers stronger encapsulation. A whole new mechanism based on accessibility and readability is enforced deep within the JVM.

Java 模块系统还提供了 OSGi 不具备的功能,主要涉及迁移到模块。OSGi 中不存在自动模块的对等物。并不是所有的 Java 库都提供(正确的)OSGi 元数据,这意味着补丁或者 pull 请求是 OSGi 使用这些库的唯一方法。库有时候也会包含一些代码,而这些代码在 OSGi 独立的类加载设置中不起作用。在 Java 模块系统中,类加载以向后兼容的方式实现。由于 Java 模块系统不使用类加载器进行隔离,因此它提供了更强的封装。在 JVM 内部强制执行了基于可访问性和可读性的全新机制。

Time will tell whether adoption of the Java module system will be higher. Because modules are now part of the Java platform itself, we fully expect the Java community to take modularization seriously.

时间会告诉我们采用 Java 模块系统是否会更好。由于现在模块是 Java 平台本身的一部分,因此期望 Java 社区认真对待模块化。

14.2 Java EE

Modules are a Java SE feature. Many developers, however, develop Java EE applications. In Java EE, Web Archives (WARs) and Enterprise Archives (EARs) bundle JAR files with deployment descriptors. These Java EE applications are then deployed onto application servers.

模块是一个 Java SE 功能。然而,有许多开发人员开发 Java EE 应用程序。在 Java EE 中,WebArchive(WAR)和 Enterprise Archive(EAR)将 JAR 文件与部署描述符捆绑在一起。然后,再将这些 Java EE 应用程序部署到应用程序服务器上。

How will Java EE evolve, given modules are now part of the Java platform? It’s fair to assume that at some point the Java EE specification will embrace modules. At the moment, it’s too early to tell what this will look like. The Java EE 8 release builds on Java SE 8. So modules and Java EE meet at the earliest in Java EE 9, for which no release date is set. Until this convergence, Java EE application servers can keep on using the classpath as before.

鉴于现在模块是 Java 平台的一部分,Java EE 将如何发展呢?可以合理地假设一下在未来的某个时刻 Java EE 规范中将包含模块。目前确定 Java EE 是什么样子还为时过早。Java EE 8 版本建立在 Java SE 8 之上。因此模块和 Java EE 最早在 Java EE 9 中融合,并且没有设置发布日期。但在此融合之前,Java EE 应用程序服务器可以像以前一样继续使用类路径。

You already saw in “Container Architectures” that in principle, the module system has features to support application containers such as Java EE application servers. What modular Java EE applications will look like remains to be seen. For applications, Java EE modularity could be a form of modular WAR or EAR files, or something completely new.

在 6.3.4 节中看到,原则上,模块系统具有支持应用程序容器(比如,Java EE 应用程序服务器)的功能。模块化的 Java EE 应用程序看起来像什么还有待观察。对于应用程序来说,Java EE 模块化可能是模块化 WAR 或 EAR 文件的一种形式,也可能是一种全新的形式。

A good first step would be to publish the Java EE APIs as modules with standardized names. The official JAX-RS API distribution already has a module descriptor. With modules, it becomes even more attractive to regard Java EE as a loosely related set of specifications. Maybe there’s no need to wait for monolithic application servers to support a full suite of specifications at once. When you can require just the parts of Java EE you need, new doors open. Keep in mind, though, this is all speculation based on what could be, not what is.

良好的第一步是将 Java EE API 作为带有标准化名称的模块发布。官方的 JAX-RS API 发行版已经有了一个模块描述符。通过使用模块,可以将 Java EE 视为一组松散的相关联的规范。没有必要等待整个应用程序服务器一次性支持所有规范。当可以请求所需的部分 Java EE 时,就打开了一扇新的大门。不过,请记住,这一切都是基于可能性而进行的推测。

14.3 Microservices 微服务

In the past few years, microservices have gained prominence as an architectural style. One of the cited benefits of microservices is that they enable modular development. By dividing a system into independently deployable and runnable parts, the system is modularized. Each microservice runs as an independent process. Multiple microservices can even be written in completely different technologies. They communicate over the network by using standard protocols such as HTTP and gRPC.

在过去几年中,微服务作为一种架构风格已经取得了显著的地位。引用微服务的好处之一是它们可以实现模块化开发。通过将系统划分为可独立部署且可运行的部分,对系统实现了模块化。每个微服务都作为一个独立的过程运行,甚至可以用完全不同的技术来编写多个微服务。它们通过使用诸如 HTTP 和 gRPC 的标准协议在网络上进行通信。

Indeed, this architectural style inherently enforces strong module boundaries. How do microservices fare on the other two principles of modularity, well-defined interfaces and explicit dependencies? Interfaces between microservices fall on a spectrum from rigidly defined in an Interface Definition Language (IDL) such as Protocol Buffers, RAML, or even WSDL/SOAP, to unspecified JSON over HTTP. Dependencies between microservices typically arise by dynamic discovery at run-time. Not many microservices stacks offer statically verifiable dependencies akin to requires in module descriptors.

事实上,这种架构风格固有地强化了模块边界。微服务如何满足模块化的其他两个原则,即定义良好的接口和显式依赖关系?定义微服务之间的接口的方式很多,从在接口定义语言(Interface Definition Language, IDL),比如 Protocol Buffers、RAML 或者 WSDL / SOAP 中的严格定义到 HTTP 上未指定的 JSON。微服务之间的依赖关系通常是在运行时通过动态发现而产生的,没有多少微服务栈提供了类似于模块描述符中 requires 的静态可验证依赖关系。

In this book, you have seen that modularity can be achieved without resorting to process isolation. Using the Java module system, strong encapsulation and explicit dependencies between modules can be enforced by the Java compiler and JVM. Use of Java interfaces and services with their explicit provides/uses wiring in the module system completes this modular development approach. Viewed from this perspective, microservices are somewhat like modules with a network boundary in between. But these network boundaries turn a microservices system into a distributed system with all the associated drawbacks. When you choose microservices solely for their modularity characteristics, think twice. Using Java modules for your system brings similar (or even stronger) modularity benefits, without the operational complexity of a microservices architecture.

在本书中已经看到,在无须过程隔离的情况下可以实现模块化。通过使用 Java 模块系统,Java 编译器和 JVM 可以强制实现模块之间的强封装和显式依赖。借助于模块系统中的显式 provides/uses,可以使用 Java 接口和服务完成这种模块化开发方法。从这个角度来看,微服务有点像带有网络边界的模块。但是这些网络边界将微服务系统变成了带有相关缺点的分布式系统。如果仅仅为了模块化特性而选择微服务,那么请仔细考虑一下。使用 Java 模块可以为系统带来类似(甚至更强大的)模块化优势,同时没有微服务架构的操作复杂性。

To be fair, there are many reasons why microservices can be a good choice, besides the argument for modularity. Think of independent updates and scaling of services, or when using different technology stacks per service really is beneficial. Then again, choosing between modules or microservices doesn’t have to be either/or. Modules can create a strong internal structure for a microservice implementation, allowing it to scale beyond what you would typically ascribe to micro. It’s quite sensible to initially develop a system with modules, while at a later stage extracting some of those modules into their own microservices when operational concerns demand it.

公平地讲,除了模块化的原因之外,还有很多其他的原因使之选择微服务。考虑独立的更新和服务的扩展,或者每个服务使用不同的技术堆栈确实是有益的。此外,在模块或微服务之间进行选择不一定非此即彼。模块可以为微服务实现创建一个强大的内部结构,使其能够超越通常所认为的微观尺度。初始开发一个带有模块的系统是相当明智的,而在后续阶段,当操作需要时可以将一些模块提取到自己的微服务中。

14.4 Next Steps 下一步

We’ve discussed several alternative approaches to modularization and how they relate to the Java module system. The hard part still lies in correctly decomposing the domain of your application, regardless of what technique you use to do so. Java modules are another powerful tool in the toolbox to create well-structured systems.

前面已经讨论了几种可选的模块化方法以及它们与 Java 模块系统的关系,不管使用什么样的技术,最困难的部分仍然在于正确分解应用程序的域。Java 模块是工具箱中另一个强大的工具,可以创建结构良好的系统。

Is the current state of the module system perfect? Nothing is, and the module system is no exception. Adding a module system this late in the Java platform’s life cycle inevitably led to compromises. Still, Java 9 lays a solid foundation to build upon. Sure, it would be great to address issues such as running multiple versions of a module at the same time besides what’s currently possible with ModuleLayer. The fact that this is not currently supported on the module path doesn’t mean it will never be. For other features, the same holds. The module system is not done and will almost certainly gain new powers in subsequent releases. For now, it is up to the Java ecosystem and tool vendors to support modular Java.

模块系统的当前状态是否完美呢?没有什么系统是绝对完美的,模块系统也不例外。在 Java 平台的生命周期中加入一个模块系统不可避免地会导致一定的损害。但 Java 9 仍然打下了一个坚实的基础。当然,ModuleLayer 除了目前的用处之外,解决诸如同时运行多个版本的模块等问题也是非常合适的。虽然当前在模块路径上并不支持 ModuleLayer,但这并不意味着其永远不会被支持。对于其他功能也是一样。模块系统还没有完成,可以肯定的是在后续版本中将会获得新的功能。目前,Java 生态系统和工具供应商都支持模块化 Java。

As the Java community starts to embrace the module system, more libraries will become available as modules. This makes it easier to embrace modules in your own applications, although we have seen that automatic modules are an acceptable interim solution. To get started, it makes sense to gain experience with the module system in a greenfield setting. Build a small application consisting of a handful of modules, like the EasyText application you’ve seen throughout this book. This gives you a good feel for what a cleanly modularized application looks like. With this experience under your belt, you can take on a more ambitious step, such as modularizing one of your existing applications.

随着 Java 社区开始接受模块系统,更多的库将变为模块。这使得在自己的应用程序中使用模块变得更容易,虽然前面已经讲过自动模块是可以接受的临时解决方案。在一个新的环境中,首先获得模块系统的使用经验是有意义的。可以像本书中所看到的 EasyText 应用程序一样构建一个包含少量模块的小应用程序,从而了解一个“干净”的模块化应用程序应该是什么样子的。有了这些经验之后,就可以采取更加“雄心勃勃”的步骤,如模块化现有的应用程序。

Having a module system as part of the Java platform is a game-changer. No, this is not something that will happen overnight. It will take considerable time for the Java community to embrace the concepts of the Java module system. That’s just the nature of the beast: modularity is not a quick fix or new feature you can slap onto existing codebases. Yet the advantages of modularity are evident. The renewed attention for modularity through microservices illustrates that most people intuitively grasp this. With the module system, Java developers gain new options for building maintainable large-scale systems.

将模块系统作为 Java 平台的一部分是一个改变“游戏规则”的尝试,这不是一夜之间就会发生的。Java 社区需要相当长的时间才能接受 Java 模块系统的概念,这是人的本性所造成的:模块化不是可以立马添加到现有代码库中的快速修复或新功能。然而模块化的优点是显而易见的。通过微服务重新关注模块化说明了大多数人直观地把握到这一点。通过模块系统,Java 开发人员获得了构建可维护的大型系统的新选择。

You now have learned the concepts of the Java module system. More important, you know the principles behind modularity and how to apply them by using the module system. Now it’s time to use this knowledge in practice. May your software have a modular future!

到目前为止,已经了解了 Java 模块系统的概念。更重要的是,知道了模块化背后的原理以及如何使用模块系统来进行模块化。现在是时候在实践中使用这些知识了。愿你所创建的软件有一个模块化的未来!