java模块系统_Java 7模块系统问题

java模块系统

新的Java模块系统最近受到了很多关注。 看完Devoxx上有关项目Jigsaw的演示后,我很兴奋,认为这可以解决复杂的类路径版本控制问题,JAR地狱等问题。开发人员最终可以使用他们想要的任何Xalan版本,而不必强迫使用。认可的机制。 不幸的是,通往更高效的模块系统的道路还不是很清晰。

在检查实际问题之前,让我们看一些基本概念:

模块化

模块化是解决复杂性的绝佳工具。 将应用程序分为多个部分(模块,库,捆绑包,子项目,组件)并分别征服它们是很有用的。 模块化的最终目标是拥有一组定义的API,用于模块之间的通信。

如果仅通过使用此API来实现所有模块间通信,则模块将松散耦合,因此:

  • 更改模块的实现很容易,并且
  • 分别开发和测试模块很容易。

它类似于面向对象的范例。 在OOP中,理想的情况是拥有许多小的,可重复使用的,简单的且分隔良好的对象。 在模块系统中,理想的是具有较小的,可重复使用的,简单的且分隔良好的模块。 只是规模不同,想法和动机是完全一样的。

逻辑分离

传统上,有两种关于如何在Java中实现模块化的方法。 逻辑分离是最自然的方法。 它包括将应用程序划分为逻辑模块(子项目),然后将它们作为一个应用程序进行部署。 仅通过定义正确的程序包结构就可以完成逻辑分离,但将应用程序拆分为几个归档文件(JAR)更为常见。 逻辑分离有助于模块的重用,并有助于实现模块之间的较低耦合。 甚至可以定义一个API并声明仅使用给定API即可实现模块之间的所有通信。 这个概念有一个大缺点。 很难施加这种限制。 没有任何机制可以强制使用API​​。 没有办法区分仅应由给定模块和属于公共API的类使用的类。 如果一个类是“公共”类,则任何其他类均可使用它,无论它属于哪个模块。 另一方面,受保护的或封装的可见性对于在模块内部使用太过严格。 通常,模块由几个包组成,并且包中的类需要能够相互调用。 因此,即使一个应用程序由几个逻辑模块组成,这些模块通常也是如此耦合,以至于分离几乎没有用。

物理隔离

另一种传统方法是物理隔离。 通过将应用程序拆分为组件,并将每个组件部署到单独的JVM中,可以强制执行分离。 然后,组件可以使用远程处理工具(如RMI,CORBA或WebServices)进行通信。 这样,可以实现分离和松散耦合。 缺点是开销很大。 仅使用远程处理来强制分离是过大的。 这使开发和部署变得不必要的复杂。 性能影响也不容忽视。

模块系统

模块系统介于逻辑和物理隔离之间。 它强制执行模块分离,但是将模块部署到同一JVM中,并且它们之间的通信由普通的旧方法调用组成。 因此,没有运行时开销。 Java生态系统中最受欢迎的模块框架是OSGi 。 这是一个成熟的规范,具有多种实现。 在OSGi模块中称为捆绑,每个捆绑相当于一个JAR。 每个捆绑软件还包含一个META-INF / MANIFEST.MF文件,该文件声明要导出的软件包和要导入的软件包。 其他捆绑包只能使用导出包中的类,捆绑包中的所有其他包都是内部的,并且其类只能在捆绑包中使用。

例如,考虑以下声明:

Manifest-Version: 1.0
Import-Package: net.krecan.spring.osgi.common
Export-Package: net.krecan.spring.osgi.dao
Bundle-Version: 1.0.0
Bundle-Name: demo-spring-osgi-dao
Bundle-SymbolicName: net.krecan.spring-osgi.demo-spring-osgi-dao

它指定包demo-spring-osgi-dao,该包要求net.krecan.spring.osgi.common包中的类,并从net.krecan.spring.osgi.dao包中导出类。 换句话说,声明说其他模块只能使用net.krecan.spring.osgi.dao包。 相反,此模块仅需要使用net.krecan.spring.osgi.common软件包,而OSGi则需要提供导出该软件包的模块。 当然,在导入和导出声明中都可以声明一个以上的包。

需要注意的重要一点是,OSGi的模块化是建立在Java之上的。 它不是语言的一部分! 尽管模块分隔可以由GUI强制执行,但编译器不强制执行模块分隔。 需要OSGi容器才能运行基于OSGi的应用程序。 容器可以像Spring DM Server中一样是运行时环境的一部分,也可以嵌入到应用程序中。 容器不仅强制执行分离,而且还提供其他服务,例如安全性,模块管理和生命周期管理。 OSGi还提供了许多其他有趣的功能,但它们超出了本文的范围。

关于JSR-277的提议,存在很多争议,该提议部分复制了OSGi。 几个月来,双方专家一直在倡导哪种更好,直到宣布放弃JSR-277并引入新的模块系统(应该是Java 7的一部分)。

JSR-294

新模块系统的第一部分是JSR-294 aka超级软件包。 该规范使模块的概念成为Java语言的一部分。

JSR-294引入了新的可见性关键字“模块”。 如果成员具有此可见性,则意味着它仅对同一模块的成员可见。 它允许创建仅由模块本身使用的内部API。 如我所见,仅在声明公共API时才应使用“ public”关键字。 在所有其他情况下,应使用“模块”或更严格的可见性。 当然,一旦在语言中有“ module”关键字,编译器将检查模块之间的可见性约束。

JSR-294也将允许依赖项定义。 在给定的版本中,可以定义一个模块依赖于另一个模块。 例如:

//org/netbeans/core/module-info.java
@Version("7.0")
@ImportModule(name="java.se.core", version="1.7+")
module org.netbeans.core;

后者意味着模块“ org.netbeans.core”依赖于1.7及更高版本的“ java.se.core”。 它等效于Maven依赖项或OSGi导入。 您可能应该忽略语法,因为它可能会更改。 这里重要的是模块依赖关系在module-info.java文件中定义,并将被编译为类文件。 在OSGi中,依赖性在纯文本文件中定义。

拼图项目

拼图项目是拟议模块系统的第二部分。 我假设它将是Sun特定于JSR-294的实现。 但这也将是Sun JDK的模块化。 由于需要使整体式JDK模块化 ,因此Sun希望将标准库拆分为模块。 这将允许直接在JRE中简化配置文件。 手机上可能会有完整的JRE,其中包含除Swing之外的所有内容。 也有可能在语言中引入新的标准API,而不必等待整个平台的新版本。 它看起来很有前途!

但是,这也是我的第一个担忧。 马克·雷因霍尔德(Mark Reinhold)注意到, 专有曲线锯与JSR标准之间界线不清楚

这项工作[Jigsaw]将有必要创建一个简单的低级模块系统,其设计将集中于模块化JDK的目标。 该模块系统可供开发人员以其自己的代码使用,并且将得到Sun的完全支持,但它不是Java SE 7 Platform Specification的正式组成部分,并且可能不受其他SE 7实现的支持。

该陈述不清楚,并留下解释的余地​​。 这是否意味着可以创建模块,但只能在Sun JRE中使用它们? 这是否意味着,如果开发人员将编写'@ImportModule(name =“ java.se.core”,version =“ 1.7+”)',它就可以在Sun JRE上运行,但可能不受IBM JRE支持? 这是否意味着Sun将以一种方式拆分其JRE,以另一种方式拆分Oracle? 我们希望不要为了“一次编写,随处运行”的原则。

问题似乎更加严重。 目前尚不清楚拼图项目的主要目标是什么。 提到了主要目标是Sun JRE的模块化,但是在这种情况下,不需要更改语言。 Sun可以将其JRE模块化,而无需更改Java语言。

这些语言更改是否仅仅是Sun JRE模块化的副产品? 如果这是真的,那就错了! 语言更改必须是一流的公民,而不是某些专有工作的副产品。

依存关系

我的另一个担心是依赖关系。 如果模块系统管理依赖项,则不再需要classpath。 一方面,这很棒。 类路径通常导致所谓的JAR地狱 。 另一方面,类路径非常灵活。 恐怕不可能用静态模块依赖项替换类路径。 让我们看看为什么:

部署时间依赖性

在Java中,有两个类路径。 有一个构建路径,在构建时使用,然后有一个类路径,在运行时使用。 它们几乎相同,但不完全相同。 经典示例是JDBC驱动程序。 无需在构建时指定JDBC驱动程序。 JDBC接口是核心Java库的一部分。 但是必须在运行时在类路径中具有JDBC驱动程序。 如今,当程序员想要更改数据库时,他只需在配置文件中更改驱动程序类名称,然后将驱动程序jar文件添加到类路径即可。 如果必须在编译时指定所有依赖项,他将无法执行此操作! 当然,在Java EE中,他可以使用JNDI数据源,但是Java SE中没有类似的东西,并且在每次需要更改JDBC驱动程序时重新编译应用程序都是不可行的解决方案。

通常甚至无法重新编译。 在某些组织中,最终的应用程序是由称为应用程序组装程序的人员从模块中组装而成的。 他没有源代码,只是将JAR放在一起,更改配置文件并创建最终程序包。 Java EE规范中甚至指定了应用程序组装者角色

可选依赖项

类似的问题是可选依赖项。 假设我们正在使用log4j之类的日志记录框架。 该库能够登录JMS,因此JMS软件包必须在构建路径中。 但是99%的用户没有使用JMS日志记录,因此他们在类路径中不需要依赖项。 必须有某种机制来处理这种情况。 为了构建模块,需要一个库,但是对于最终用户,此依赖性是可选的。 当然,在理想环境中,JMS功能将在单独的模块中,但是我们并不生活在理想环境中,有时以这种方式拆分项目并不现实。

依赖冲突

另一个大问题是依赖冲突。 如果您与Maven合作,那么您就会知道我在说什么。 普通的企业应用程序由数十个第三方库组成,它们都具有依赖关系,有时这些依赖关系存在冲突。 例如,开发人员想要使用依赖于commons-collections 2.1.1的Hibernate。 他还希望使用取决于commons-collections 2.1的commons-dbcp。 开发人员或应用程序组装者必须决定在这种情况下该做什么。 他可以决定自己只想在应用程序中的每个地方都使用一个特定版本的库,也可以决定在应用程序的不同部分中使用不同的版本是令人满意的。 重要的是这种情况不能自动解决。 必须由知道该模块在给定应用程序中如何使用并认识到版本之间可能存在的不兼容性的人来决定。

关于Java依赖关系,有很多事情可以说,这超出了本文的范围,但是要保留的主要要点是它们不是静态的! 可以使用一组库构建应用程序,并以完全不同的一组库运行该应用程序。 每个模块化系统都必须以一种或另一种方式处理这种情况。 Maven在如何配置依赖项,如何处理依赖项冲突等方面有很多配置选项。但是它仍然只是一个构建系统。 在最坏的情况下,仍然可以手动配置类路径。 OSGi处于相反的情况。 它仅处理运行时(部署时)依赖项,但不处理构建时。 新的Java模块系统将同时用于构建和运行时(我假设)。 它给已经很复杂的问题带来了更大的复杂性。

结论

当然,我不认为Sun工程师想要破坏Java。 我知道他们希望使Java更好,更易于使用,但我只是担心政治和市场方面的理由要比技术方面的要强。 再有一段时间,这不仅是API更改或Sun特定的更改。 这将是语言的改变! 语言一旦更改,一旦添加了“模块”关键字,便无法回头。 Java中将有一个模块系统,无论我们是否喜欢它,我们都必须使用它。 很难想象会有一个模块化的JVM,即该语言中的“ module”关键字的情况,而我们仍将在其之上使用OSGi。

参考资料

Lukas Krecan是一位自由Java EE开发人员。 他在现实世界中的公司工作,但在业余时间里,他在虚构的完美世界中撰写有关编程的博客

翻译自: https://www.infoq.com/articles/java7-module-system/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

java模块系统

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值