编程技术是改变世界的力量。
本站
当前位置:网站首页 > 后端语言 > 正文

不休不眠的深入探究模块系统高级特性,发现了新世界!

gowuye 2024-04-03 16:14 3 浏览 0 评论

目前为止,我们用requires指令表示模块之间的依赖关系,其中模块必须按名称引用每个特定的依赖。正如之前详细解释的那样,这是可靠配置的核心,但有时你需要更高层次的抽象。

本次将探讨模块系统中的服务,以及如何使用服务消除模块之间的直接依赖关系,以实现模块之间的解耦。使用服务解决问题的第一步是掌握基础知识。接下来,本文将研究其细节,特别是如何正确地设计服务,以及如何使用JDKAPI来消费服务。

读完本文,你将了解如何设计好服务、如何为使用或提供服务的模块编写模块声明,以及如何在运行时加载服务。借助这些技能,你可以使用JDK或第三方依赖中的服务,以及移除自己项目中的直接依赖。

一 探索对服务的需求

如果本文讨论的是类而不是模块,你是否乐于总是依赖于具体的类型?

或者必须在类中实例化每个依赖?如果你喜欢诸如控制反转和依赖注入之类的设计模式,那么此时应该强烈地摇头。调用者可以选择最合适的流处理工具,甚至可以自由选择任何InputStream的实现。

代码清单10-1 依赖于具体类型建立依赖关系

代码清单10-2 依赖于抽象类型;调用者建立依赖关系

依赖接口或抽象类,让其他人选择具体实例的另一个重要好处是,这样做会逆转依赖关系的方向。与高级概念(比如Department)依赖于低级细节(Secretary、ClerkManager)不同,两者都可以依赖于抽象(Employee)。

如图10-1所示,这打破了高级概念和低级概念之间的依赖关系,从而将它们解耦。

如果一个类型建立了自己的依赖关系(上),用户就不能对这些关系进行更改。如果在构造期间传递类型的依赖(下),那么用户可以选择最适合的实现回到模块,它们在不同的抽象级别上。

1)模块依赖于其他具体模块。

2)用户无法更改依赖。

3)没有办法反转依赖关系。

幸运的是,模块系统没有这样做。模块系统提供了服务,一种让模块表示其依赖于抽象类型,或可以提供实现依赖的具体类型的方法,而模块系统位于中间,在它们之间进行协商(如果你现在想到的是服务定位器模式,那就完全正确了)。

下文将提到,尽管服务并不能完美地解决所有问题,但它确实已经解决了许多问题,图10-2展示了两种类型的依赖关系。

如果一个模块依赖于另一个模块(上),那么依赖是固定的,不能从外部改变;另一方面,如果模块使用服务(下),那么在运行时可选择最合适的具体实现。

二 JPMS中的服务

当在JPMS的背景中讨论服务时,会涉及想要使用的特定类型,它通常是一个接口,但是人们没有将它的实现实例化。相反,模块系统采用宣称实现了对应功能的其他模块,并将实现实例化。

本节将详细介绍该流程的工作原理,以便你了解应该在模块描述符中放入什么、如何在运行时获取实例,以及这将如何影响模块解析。

1 使用、提供和消费服务

服务是一个模块想要使用的可访问类型,而另一个模块提供了实现实例。

1) 消费服务的模块在其模块描述符中使用uses ${service}指令表示其需求,其中${service}是服务类型的完全限定名。

2) 提供服务的模块用provides ${service} with ${provider}指令来表示其提供服务,其中${service}与uses指令中的类型相同,而${provider}是另一个类的完全限定名称。该类可以是以下两个类中的任何一个。

1.扩展或实现${service},并具有公有无参构造函数(被称为提供程序构造器)的具体类。

2.使用公有、静态、无参数的方法并返回任意类型,该类型扩展或实现了${service}(被称为提供者方法)。

运行时,依赖模块可以通过ServiceLoader类调用ServiceLoader.load(${service}.class),以获取服务的所有提供者实现。然后,模块系统为模块图中声明的每个提供者返回一个Provider<${service}>,图10-3演示了提供者的实现。

使用服务的核心是特定的类型,这里称为ServiceProvider类实现了Service,其模块声明包含provides-with指令。消费服务的模块需要使用uses指令。在运行时,可以使用ServiceLoader获取给定服务的所有提供者实例。

尽管围绕服务有很多细节需要考虑,但一般来说,服务是一个很好的抽象概念,并且在实践中使用很方便,所以本节从这里开始。实施服务比输入一个requiresexports指令要花更长的时间。

ServiceMonitor应用程序为实践服务提供了一个完美的示例。monitor模块中的monitor类需要List<ServiceObserver>与其监视的服务之间进行通信。到目前为止,Main的工作如下。

代码的具体工作方式并不十分重要。与之相关的是,它使用monitor.observer.alpha模块中的具体类型AlphaServiceObservermonitor.observer.beta模块中的类型BetaServiceObserver

因此monitor模块需要依赖于这些模块,并且这些模块需要导出相应的包。图10-4展示了模块图中的相关部分。

在没有服务的情况下,monitor模块需要依赖所有其他相关的模块:observeralphabeta,如部分模块图所示.

现在把注意力转向服务。第一步,创建这些observer的模块需要声明依赖一个服务,并且使用ServiceObserver,因此monitor的模块声明如下。

下一步是提供者模块monitor.observer.alphamonitor.observer.beta进行provides指令声明。

这样并不能正常工作,编译器会报告如下错误。

提供者构造函数和提供者方法必须是无参数的,但是AlphaServiceObserver需要观察服务的URL,这该怎么办?你可以在创建后再设置URL,但这样不仅会让类变得不确定,还会产生一个问题:如果服务不是alpha该怎么办?

因此,不应该这样做,而应该创建observer的工厂方法,该方法仅在URL正确的情况下返回一个实例,这样更简洁。

因此,在monitor.observer中创建一个新的接口ServiceObserverFactory。它只有一个方法createIfMatchingService,该方法接收服务URL并返回一个Optional<ServiceObserver>

monitor.observer.alpha和monitor.observer.beta模块中分别创建实现,以执行AlphaServiceObserverBetaServiceObserver上的静态工厂方法应该做的工作,图10-5显示了模块图的对应部分。

通过服务,monitor只依赖定义服务的模块observer,而不再直接依赖提供服务的模块alphabeta。

使用这些类,你能以服务的方式提供和消费ServiceObserverFactory类。代码清单10-3展示了monitor、monitor.observer、monitor.observer.alpha和monitor.observer.beta的模块声明。

代码清单10-3 使用ServiceObserverFactory的4个模块

最后一步是在monitor中获得observer工厂。为此,调用ServiceLoader.load(ServiceObserverFactory.class),对返回的提供者进行流处理,得到服务实现。

就是这样:有一堆服务提供者,而模块消费者和模块提供者对彼此一无所知,它们唯一的联系是二者依赖于API模块。

平台模块还声明和使用了大量的服务。一个特别有趣的例子是由java.sql模块声明和使用的java.sql.Driver

这样,java.sql可以访问其他模块提供的所有Driver实现。

平台中使用服务的另一个典型例子是java.lang.System.LoggerFinder

这是Java 9中新添加的一个API,它使用户能将JDK(而不是JVM)的日志消息导入所选择的日志框架(例如Log4JLogback)。

JDK使用LoggerFinder创建Logger实例,然后用这些实例记录所有消息,而不是输出到标准输出中。

Java 9及以上版本中,日志框架可以实现日志的工厂方法,并在工厂方法中利用框架的基础设施。

但是日志框架如何将LoggerFinder的实现通知给java.base呢?很简单,它们为LoggerFinder服务提供自己的实现。

这之所以行得通,是因为基本模块使用LoggerFinder,然后调用ServiceLoader来定位LoggerFinder的实现。它获得了一个特定于框架的查找器,借助它创建Logger实现,然后使用这些实现来记录消息。

这将使你对创建和使用服务在细节上有一个更加清晰的认识。

2 服务的模块解析

如果你曾经启动过一个简单的模块化应用程序,并且观察过模块系统正在做什么(例如,使用--show-module-resolution),那么可能会对所解析的平台模块的数量感到惊讶。

ServiceMonitor这样的简单应用程序而言,唯一的平台模块应该是java.base,最多有一两个其他模块。那么为什么有这么多其他模块呢?

答案就是服务。

要点 请记住,之前曾介绍过,只有在模块解析期间进入模块图的模块在运行时才可用。为了确保对服务的所有可见提供者而言都是这样,解决方案需要考虑usesprovides指令。除之前描述的解析行为外,一旦解析到一个使用服务的模块,它将把所有可观察的模块添加到提供该服务的图中,这被称为绑定。

用选项--show-module-resolution启动ServiceMonitor应用程序会出现大量的服务绑定。

monitor模块绑定了monitor.observer.alphamonitor.observer.beta模块,但是并不依赖于它们中的任何一个。

归因于java.base和其他平台模块,同样的情况也适用于jdk.charsets、jdk.localedata以及更多模块。图10-6展示了相关的模块图。

服务绑定是模块解析的一部分:一旦某个模块(比如monitorjava.base)被解析,它的uses指令会被分析,并且提供所对应的服务的所有模块(alphabeta以及charsetslocaledata)都会被添加到模块图中用--limit-modules排除服务。

服务和--limit-modules选项之间具有有趣的交互。之前讲过,--limit-modules将可见模块全集限制到指定的范围(包括传递依赖),但这并不包含服务!除非--limit-modules选项所列出的模块传递性地依赖于提供的服务,否则它们是不可见的,也不会被放入模块图中。在这种情况下,对ServiceLoader::load的调用通常会一无所获。

如果像检查模块解析那样启动ServiceMonitor,但是将可见模块的范围限制为所有依赖monitor的模块,输出则会更简单。

就是这样了:输出中没有任何服务——既没有observer工厂,也没有平台模块通常绑定的那些服务。图10-7展示了本示例简化的模块图。

通过选项--limit-modules monitor,可见模块全集被限制为monitor模块的传递依赖,不包含图10-6中被解析的服务提供者--limit-modules--add-modules的结合体尤为强大:前者可被用于排除所有服务,后者可以用来将所期望的服务添加回来。这使人们在启动期间可以尝试不同的服务配置,而不用修改模块路径。

为什么uses指令是必须的说些题外话,在此回答开发者们一个关于uses指令的问题。为什么它是必需的?一旦ServiceLoader::load被调用,模块系统就不能直接查找服务提供者了吗?

如果模块通过服务被恰当解耦,那么提供服务的模块很可能不是任何根模块的传递依赖。如果没有进一步的措施,按惯例,服务提供者模块不会被放入模块图中。因此,在运行时,当某个模块尝试使用服务时,它将不可用。

为了让这些服务正常工作,服务提供者模块必须被放入模块图中,即使它们不受任何根模块传递地依赖也是如此。

但是模块系统如何分辨哪个模块提供服务?这是否意味着所有带provides指令的模块都可以?那样就太多了。答案是否定的,只有所需服务的提供者才会受到解析。

这样就有必要分辨服务的使用情况。分析调用ServiceLoader::load的字节码既耗时又不可靠,所以人们需要一个更加清晰的机制来确保其高效、正确,这就是uses指令。模块系统要求人们声明模块使用的服务,从而可靠且高效地使所有服务提供者模块可用。

相关推荐

PHPMailer远程命令执行漏洞分析

摘要:PHPMailer是一个强大的PHP编写的邮件发送类,但近日被爆出远程命令执行漏洞,该漏洞实际上是什么,有何种影响,本文对该漏洞进行了分析及验证方法,并给出防护方案。0x00漏洞概要PHPMa...

「安全漏洞」DedeCMS-5.8.1 SSTI模板注入导致RCE

漏洞类型SSTIRCE利用条件影响范围应用漏洞概述2021年9月30日,国外安全研究人员StevenSeeley披露了最新的DedeCMS版本中存在的一处SQL注入漏洞以及一处SSTI导致的RCE...

回顾使用PHP原生发送电子邮件(终)文件附件

FileAttachments文件附件Fileattachmentsworkjustlikemixedemail,exceptthatadifferentcontenttyp...

php-fpm.conf &amp; php.ini 安全优化实践

0x01关于php其历史相对已经比较久远了,这里也就不废话了,属弱类型中一种解释型语言除了web开发以及写些简单的exp,暂未发现其它牛逼用途,暂以中小型web站点开发为主另外,低版本的php自身...

linux 安全配置 ossec 开源检测

linux安全配置ossec开源检测一:介绍主要功能有日志分析、完整性检查、rootkit检测、基于时间的警报和主动响应。除了具有入侵检测系统功能外,它还一般被用在SEM/SIM(安全事件管理(...

PHP使用PHPMailer发送验证码邮件的方法与调用逻辑

首先我们需要下载PHPMailer:https://github.com/PHPMailer/PHPMailer一般情况下我们只需要压缩包中的src文件夹中的文件,并保存至根目录即可:设置一个文件,如...

回顾使用PHP原生发送电子邮件(一)

IwishIcouldremembertheveryfirstemailmessageIeversent.Unfortunately,thetruthisthatI...

PHPMAILER实现PHP发邮件功能php实例

这篇文章主要为大家详细介绍了PHPMAILER实现PHP发邮件功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下本文实例为大家分享了PHPMAILER实现PHP发邮件功能的具体代码,供大家参考,具...

500道网络安全面试题集锦(附答案)

本篇文章内容为网络安全各个方向涉及的面试题,星数越多代表问题出现的几率越大,但是无论如何都无法覆盖所有的面试问题,更多的还是希望由点达面,查漏补缺,然后祝各位前程似锦,都能找到自己满意的工作!一、We...

网站放家里,随处看电影「Apache+php+ssl 安装和配置」

  使用5G网络,随处都可以看到放自己家里电脑的视频。这个功能很容易实现,不需要太多的专业知识,也不需要额外花钱。如果确实需要,最多花不到两百块钱买一台旧电脑放家里,做个网站,就能解决全部问题,Fre...

Windows2008中 Magic Winmail Server提权

MagicWinmailServer是安全易用全功能的邮件服务器软件,不仅支持SMTP/POP3/IMAP/Webmail/LDAP(公共地址簿)/多域/发信认证/反垃圾邮件/邮件过滤/邮件组...

利用PHPmailer发送邮件

早上帮朋友做一个收集客户联系方式的页面,要求能实时推送信息给管理员。刚开始想到做后台管理,因为时间紧,做后台是赶不上了。想过通过短信发送,成本太高,否决了。。。灵机一动,客户提交时直接把信息发送到邮箱...

phpmailer发送邮件

phpmailer发送邮件PHP内置的mail函数使用起来不够方便,另外受其他语言的影响,博主更偏好面向对象的包管理模式,因此phpmailer成为了我用PHP发送邮件的首选,这里分享给大家。库导入这...

PHP初级教程:读取输入

PHP读取输入:Form:?formaction="welcome.php"method="post">Name:(inputtype="text...

php filter 验证Email,Url,Ip格式

今天发现一个非常好用的函数东西,filter过滤器,用于验证和过滤来自非安全来源的数据,比如用户的输入。验证Email:$email='1234567@qq.com';if(!filter_v...

取消回复欢迎 发表评论: