详解JDK9特性之JPMS模块化

简介

在module中会有元数据来描述该模块的信息和该模块与其他模块之间的关系。这些模块组合起来,构成了最后的运行程序。

听起来是不是和gradle或者maven中的模块很像?

通过组件化,我们可以根据功能来区分具体的模块,从而保持模块内的高聚合,模块之间的低耦合。

另外,我们可以通过模块化来隐藏接口的具体实现内容,从而不影响模块之间的调用。

最后,我们可以通过显示声明来描述模块之间的依赖关系。从而让开发者更好的理解系统的应用逻辑。

JDK9中模块的实现

在JDK9之前,java是通过不同的package和jar来做功能的区分和隔离的。

但在JDK9中,一切都发送了变化。

首先是JDK9本身的变化,JDK现在是以不同的模块来区分的,如果你展开IDE中JDK的依赖,会看到java.base,java.compiler等模块。

其中java.base模块比较特殊,它是独立的模块,这就意味着它并不依赖于其他任何模块,并且java.base是其他模块的基础,所以在其他模块中并不需要显式引用java.base。

我们再总结一下:

class是字段和方法的集合,package是class的集合,而module是package的集合。

一般来说使用模块和不使用模块对用户来说基本上是感觉不到的,因为你可以将模块的jar包当成普通的jar包来使用,也可以将普通的jar包当成模块的jar包来使用。

当使用普通的jar包时,JDK将会采用一种Automatic modules的策略将普通jar包当成module jar包来看待。

那么module和普通的jar包有什么区别呢?

module中包含有一个module-info.class文件,这个文件定义了module本身的信息和跟外部module之间的关系。

要使用module jar包,需要将该jar包放入modulepath而不是classpath。

下面我们在具体的例子中详细探索一下module的使用。

JDK中的module

刚刚讲到了JDK也是以模块来区分的,并且所有模块的基础都是java.base。我们可以使用java --list-modules 来列出现有的所有module:

java --list-modules 

java.base@14.0.1

java.compiler@14.0.1

java.datatransfer@14.0.1

java.desktop@14.0.1

java.instrument@14.0.1

java.logging@14.0.1

java.management@14.0.1

java.management.rmi@14.0.1

....

也可以使用java --describe-module 来查看具体module的信息:

java --describe-module java.base

java.base@14.0.1

exports java.io

exports java.lang

exports java.lang.annotation

exports java.lang.constant

exports java.lang.invoke

exports java.lang.module

exports java.lang.ref

exports java.lang.reflect

exports java.lang.runtime

...

我们再具体看一下module-info.class文件中的内容:

module java.base {
    exports java.io;
    exports java.lang;
    exports java.lang.annotation;
    ...

看到了JDK自带的module,接下来我们创建几个自己的module来看看。

创建自己的module

假如我们有一个controller,一个service的接口,和两个service的实现。

为了简单起见,我们将这四个类分在不同的module中。

在IDEA中创建一个module很简单,只需要在java文件夹中添加module-info.java文件就可以了。如下图所示:

代码其实很简单,Controller引用了Service接口,而两个Service的实现又实现了Service接口。

看下controller和service两个模块的的module-info文件:

module com.flydean.controller {
    requires com.flydean.service;
    requires lombok;
}
module com.flydean.service {
    exports com.flydean.service;
}

requires表示该模块所要用到的模块名字。exports表示该模块暴露的模块中的包名。如果模块不暴露出去,其他模块是无法引用这些包的。

看下在命令行怎么编译,打包和运行module:

$ javac

    --module-path mods

    -d classes/com.flydean.controller

    ${source-files}

$ jar --create

    --file mods/com.flydean.controller.jar

    --main-class com.flydean.controller.ModuleController.Main

    -C classes/com.flydean.controller .

$ java

    --module-path mods

    --module com.flydean.controller

深入理解module-info

上一节我们将了module-info中的requires和exports。这一节我们来更多的讲解module-info中的其他用法。

transitive

先看下modulea的代码:

public ModuleService getModuleService(){
    return new ModuleServiceA();
}

getModuleService方法返回了一个ModuleService,这个ModuleService属于模块com.flydean.service,我们看下module-info的定义:

module com.flydean.servicea {
    requires com.flydean.service;
    exports com.flydean.servicea;
}

看起来好像没什么问题,但是如果有其他的模块来使用servicea的getModuleService方法就会有问题,因为该方法返回了模块com.flydean.service中定义的类。所以这里我们需要用到transitive。

module com.flydean.servicea {
    requires transitive com.flydean.service;
    exports com.flydean.servicea;
}

transitive意味着所有读取com.flydean.servicea的模块也可以读取 com.flydean.service。

static

有时候,我们在代码中使用到了某些类,那么编译的时候必须要包含这些类的jar包才能够编译通过。但是在运行的时候我们可能不会用到这些类,这样我们可以使用static来表示,该module是可选的。

比如下面的module-info:

module com.flydean.controller {
    requires com.flydean.service;
    requires static com.flydean.serviceb;
}

exports to

在module info中,如果我们只想将包export暴露给具体的某个或者某些模块,则可以使用exports to:

module com.flydean.service {
    exports com.flydean.service to com.flydean.controller;
}

上面我们将com.flydean.service只暴露给了com.flydean.controller。

open pacakge

使用static我们可以在运行时屏蔽模块,而使用open我们可以将某些package编译时不可以,但是运行时可用。

module com.flydean.service {
    opens com.flydean.service.subservice;
    exports com.flydean.service to com.flydean.controller, com.flydean.servicea, com.flydean.serviceb;
}

上面的例子中com.flydean.service.subservice是在编译时不可用的,但是在运行时可用。一般来说在用到反射的情况下会需要这样的定义。

provides with

假如我们要在Controller中使用service的具体实现,比如servicea和serviceb。一种方法是我们直接在controller模块中使用servicea和serviceb,这样就高度耦合了。

在模块中,我们可以使用provides with来对模块之间的耦合进行解耦。

我们看下代码:

module com.flydean.controller {
    uses com.flydean.service.ModuleService;
    requires com.flydean.service;
    requires lombok;
    requires slf4j.api;
}
module com.flydean.servicea {
    requires transitive com.flydean.service;
    provides com.flydean.service.ModuleService with com.flydean.servicea.ModuleServiceA;
    exports com.flydean.servicea;
}
module com.flydean.serviceb {
    requires transitive com.flydean.service;
    provides com.flydean.service.ModuleService with com.flydean.serviceb.ModuleServiceB;
    exports com.flydean.serviceb;
}

在controller中,我们使用uses来暴露要实现的接口。而在具体的实现模块中使用provides with来暴露具体的实现。

怎么使用呢?我们在controller中:

public static void main(String[] args) {
    List<ModuleService> moduleServices = ServiceLoader
    .load(ModuleService.class).stream()
    .map(ServiceLoader.Provider::get)
    .collect(toList());
    log.info("{}",moduleServices);
}

这里我们使用了ServiceLoader来加载接口的实现。这是一种很好的解耦方式,这样我可以将具体需要使用的模块放在modulepath中,实现动态的修改实现。

要想在maven环境中顺利完成编译,maven-compiler-plugin的版本必须要在3.8.1以上。

总结

本文介绍了JDK9中module的使用,并在具体的中介绍了更多的module-info的语法。

以上就是详解JDK9特性之JPMS模块化的详细内容,更多关于JDK9特性之JPMS模块化的资料请关注我们其它相关文章!

(0)

相关推荐

  • java的jdk基础知识点总结

    在java开发中,有一个核心的组成部分,我们在进行java安装时,首先下载的就是这个软件,它就是JDK工具包.可以说在范围上是包括了java的其它组成部分,不过还需要我们对这个工具包的使用有深入的了解.下面我们就JDK的概念.组成部分.三种类型.与其他构件区别带来详解. 1.概念 JDK(Java Development Kit)是Java语言的软件开发工具包,主要用于移动设备.嵌入式设备上的Java应用程序.JDK是整个java开发的核心,它包含了JAVA的运行环境(JRE)和JAVA工具.没

  • JDK1.8中的ConcurrentHashMap源码分析

    一.容器初始化 1.源码分析 在jdk8的ConcurrentHashMap中一共有5个构造方法,这四个构造方法中都没有对内部的数组做初始化, 只是对一些变量的初始值做了处理 jdk8的ConcurrentHashMap的数组初始化是在第一次添加元素时完成 // 没有维护任何变量的操作,如果调用该方法,数组长度默认是16 public ConcurrentHashMap() { } // 传递进来一个初始容量,ConcurrentHashMap会基于这个值计算一个比这个值大的2的幂次方数作为初始

  • SpringBoot修改子模块Module的jdk版本的方法 附修改原因

    一.项目目录 即一个空项目里,有两个springboot的Module. 当需要修改kuangshen-es-api这个Module的jdk版本时,可以修改以下内容. 二.修改以下内容 1.pom.xml 修改kuangshen-es-api这个Module的pom.xml文件: <properties> <java.version>1.8</java.version> </properties> 2.Project Structure File->P

  • 手动模拟JDK动态代理的方法

    为哪些方法代理? 实现自己动态代理,首先需要关注的点就是,代理对象需要为哪些方法代理? 原生JDK的动态代理的实现是往上抽象出一层接口,让目标对象和代理对象都实现这个接口,怎么把接口的信息告诉jdk原生的动态代理呢? 如下代码所示,Proxy.newProxyInstance()方法的第二个参数将接口的信息传递了进去第一个参数的传递进去一个类加载器,在jdk的底层用它对比对象是否是同一个,标准就是相同对象的类加载器是同一个 ServiceInterface) Proxy.newProxyInst

  • jdk15的安装与配置全过程记录

    如何下载并配置JDK 15 进入官网下载JDK 15. 官网地址:https://www.oracle.com/index.html 我们下载:https://www.jb51.net/softs/745289.html 1.点击Products然后点击java. 2.向下拉并找到 -Oracle JDK,点击进入. 3.点击下载JDK 15 我的电脑是win10,所以下载的win10的.推荐下载压缩版的.zip,好装也好卸载,也可以下载.exe直接下一步下一步,记得自己的安装路径就好. 点击下

  • JDK9对String字符串的新一轮优化

    String类可以说是Java编程中使用最多的类了,如果能对String字符串的性能进行优化,那么程序的性能必然能大幅提升. 这不JDK9就对String字符串进行了改进升级,在某些场景下可以让String字符串内存减少一半,进而减少JVM的GC次数. String的底层存储 在面试的时候我们通常会说String字符串有不可变的特性,每次都要创建新的字符串.那么,为什么String字符串是不可变的呢? 先来看一下String字符串的底层存储结构: public final class Strin

  • 安装多个jdk导致eclipse打不开问题解决方案

    问题描述 本来使用的是jdk1.8,由于其他原因需要使用jdk1.6,在安装完jdk1.6后打开eclipse就会报错: Version 1.6.0_43 of the JVM is not suitable for this product.Version:1.7 or greater is required. 问题原因:jdk1.6安装完成时,会将java.exe.javaw.exe.javaws.exe放入环境变量Path的对应的某个目录中(win10是在C:\Windows\system

  • Java(JDK/Tomcat/Maven)运行环境配置及工具(idea/eclipse)安装详细教程

    Java(计算机编程语言) Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承.指针等概念,因此Java语言具有功能强大和简单易用两个特征. Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程. Java具有简单性.面向对象.分布式.健壮性.安全性.平台独立与可移植性.多线程.动态性等特点. Java可以编写桌面应用程序.Web应用程序.分布式系统和嵌入式系统应用程序等. Java环境配置(

  • Ubuntu安装jdk8常用方法流程解析

    安装方式: 1):通过ppa(源) 方式安装. 2):通过官网安装包安装.JDK官网下载地址 一:使用ppa(源)方式安装: 1):添加ppa源 sudo add-apt-repository ppa:webupd8team/java sudo apt-get update 2):安装oracle-java-installer(jdk8版本) sudo apt-get install oracle-java8-installer 注:安装器会提示是否同意条款,根据提示选择yes即可,若不想手动输

  • 详解JDK9特性之JPMS模块化

    简介 在module中会有元数据来描述该模块的信息和该模块与其他模块之间的关系.这些模块组合起来,构成了最后的运行程序. 听起来是不是和gradle或者maven中的模块很像? 通过组件化,我们可以根据功能来区分具体的模块,从而保持模块内的高聚合,模块之间的低耦合. 另外,我们可以通过模块化来隐藏接口的具体实现内容,从而不影响模块之间的调用. 最后,我们可以通过显示声明来描述模块之间的依赖关系.从而让开发者更好的理解系统的应用逻辑. JDK9中模块的实现 在JDK9之前,java是通过不同的pa

  • 详解android特性之CoordinatorLayout用法探析实例

    当我在AS上新建一个module时,系统默认的最外层布局不再是我们熟悉的五大布局中的一种,而是一个全新的布局:CoordinatorLayout.它是Material风格的重要组件, 作为布局的顶层控件,协调(Coordinate)其他组件, 实现联动. 下面来看一个最简单的例子,CoordinatorLayout与FloatingActionButton的使用,它可以使浮动按钮上下移动,为Snackbar流出空间来展示. 定义的布局文件如下: <?xml version="1.0&quo

  • 详解Vuex下Store的模块化拆分实践

    前言 最近的项目用到了 vue.js + vuex + vue-router 全家桶,版本为 >2.0,在搞Store的时候发现,圈子里大部分关于vuex的文章都是比较基础的Demo搭建方式,很少有涉及到比较复杂的模块化拆分的Store实践,而且事实上也有朋友在实践中问到过这方面的内容,vuex自身提供了模块化的方式,因此在这里总结一下我自己在项目里的心得. 模块化拆分 vue.js的项目文件结构在这里就不说了,大家可以通过vue-cli初始化项目,脚手架会为你搭建一个start项目的最佳实践.

  • Java9新特性中的模块化详解

    目录 模块化是什么? 那么,模块化怎么用呢? 为什么要用模块化 显式管理依赖: 强封装性: 安全性: 规范性: 自定义最小运行时映像: 孵化器模块的支持: Java9中的一个重大特性是增加了一种新型的程序设计组件 - 模块. 官方对模块的定义为:一个被命名的,代码和数据的自描述集合.( the module, which is a named, self-describing collection of code and data). 这个在Java7的时候就已经被提出,但由于其复杂性,不断跳票

  • JDK13的新特性之AppCDS详解

    简介 AppCDS的全称是Application Class-Data Sharing.主要是用来在不同的JVM中共享Class-Data信息,从而提升应用程序的启动速度. 通常来说,如果要执行class字节码,JVM需要执行下面的一些步骤:给定一个类的名字,JVM需要从磁盘上面找到这个文件,加载,并验证字节码,最后将它加载进来. 如果JVM启动的时候需要加载成百上千个class,那么需要的就不是一个小数目了. 对于打包好的jar包来说,只要jar的内容不变,那么jar包中的类的数据始终是相同的

  • 详解Python模块化编程与装饰器

    我们首先以一个例子来介绍模块化编程的应用场景,有这样一个名为requirements.py的python3文件,其中两个函数的作用是分别以不同的顺序来打印一个字符串: # requirements.py def example1(): a = 'hello world!' print (a) print (a[::-1]) def example2(): b = 'hello again!' print (b) print (b[::-1]) if __name__ == '__main__':

  • 详解Js模块化的作用原理和方案

    一.模块化概念 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起:块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信. 二.模块化作用 为什么要用模块化的JavaScript? 因为在实际的开发过程中,经常会遇到变量.函数.对象等名字的冲突,这样就容易造成冲突,还会造成全局变量被污染: 同时,程序复杂时需要写很多代码,而且还要引入很多类库,这样稍微不注意就容易造成文件依赖混乱: 为了解决上面的的问题,我们才开始使用模块化的js,所以说模块

  • Java8新特性Stream流实例详解

    什么是Stream流? Stream流是数据渠道,用于操作数据源(集合.数组等)所生成的元素序列. Stream的优点:声明性,可复合,可并行.这三个特性使得stream操作更简洁,更灵活,更高效. Stream的操作有两个特点:可以多个操作链接起来运行,内部迭代. Stream可分为并行流与串行流,Stream API 可以声明性地通过 parallel() 与sequential() 在并行流与顺序流之间进行切换.串行流就不必再细说了,并行流主要是为了为了适应目前多核机器的时代,提高系统CP

  • Linux正则表达式特性详解及BRE与ERE的异同点

    Linux正则表达式(Regular Expression)主要遵从POSIX BRE或者POSIX ERE标准.什么是POSIX呢,POSIX Portable Operating System Interface 可移植操作系统接口ERE是BRE的扩展版本,具体更强的处理能力,并增加了一些元字符(metacharactor). BRE主要的能力集有: 1) 普通字符(Literal text),如a,b,c等 2)非打印字符,包括TAB,回车,换行,回车换行(WINDOWS) 3)任意字符.

  • InnoDb 体系架构和特性详解 (Innodb存储引擎读书笔记总结)

    后台线程 •Master Thread 核心后台线程,主要负责将缓冲池的数据异步刷新到磁盘.例如脏页的刷新,插入缓冲的合并,undo 页的回收等. 每秒一次的操作: 1.日志缓冲刷新到磁盘,即使该事务还没有提交.该操作总是会发生,这个就是为了再大的事务,提交时间都很短. 2.当IO压力很小时(1s内发生的IO次数小于5% innodb_io_capacity)合并5% innodb_io_capacity 的插入缓冲. 3.当脏页比例大于 innodb_max_dirty_pages_cnt,

随机推荐