美文网首页Javajava新特性
java 9 功能的新特性 (一)模块化

java 9 功能的新特性 (一)模块化

作者: 樂浩beyond | 来源:发表于2017-10-06 22:02 被阅读564次

    Oracle 在 9月22 日终于发布了 jdk 9 (难产了好长时间啊%>_<%),java在走过了22个年头之后迎来了新的版本,作为一门使用率排在第一的编程语言(截止到现在10月份), What 's new ?? 话不多说,让我们看一下这次发布带给我们的一个主要新功能——模块化。

    Jigsaw

    Modularity(模块化)是第一个要介绍的,代码名称为Jigsaw。作为这次发布的重点功能,也是本文详细介绍的。其实在jdk 7的时候Oracle就考虑引入了,但是这中间考虑到稳定性、安全性等问题直到 jdk 9 才引入。
    首先介绍一下模块化概念:

    简单说模块是代码和数据的封装体。这里的代码指的是一些包含类型的Packages 。Packages是类路径名称,模块就是一个或者多个Package组成的封装体。

    说完了概念,那么为什么Oracle要抛出这样一个概念呢?

    • 改进组件间的依赖管理,引入比Jar粒度更大的Module
    • 使得java SE程序更加容易轻量部署
    • 改进性能和安全性

    先说第二点如何理解,请看下面这张图:

    image.png

    运行一个简单的hello world程序,在JDK8中你需要几百兆的JRE环境,有些jar可能并不是你运行程序所必须的,浪费了很多空间。但是在 JDK 9中,JDK被分成了大约94个modules,在用的时候引入你需要的module就行了。

    对于第一点如何理解呢?其实这点说的是解决 java 中 ““classpath hell”” 或者说“jar hell”的问题。看到这两个名词我也是一脸懵逼啊?!what the hell !

    PS: ““classpath hell”” 和“jar hell”在本质上相同,只是后者更加关注由复杂类加载层级产生的问题。

    image.png

    JAR Hell

    JAR Hell 问题源自java中的类加载机制。它主要表现出四个问题:
    1、无法表述的依赖关系(Unexpressed Dependencies)和传递依赖(Transitive Dependencies)
    首先一个 jar 包是不能通过某种方式去告诉JVM 我依赖哪些其他 jar包的。 需要开发者自己通过读文档,判断依赖关系,下载缺失的依赖jar包。而且只有在运行时需要用到依赖jar包时,才会报 NoClassDefFoundError的错误。传递依赖指的是应用程序需要依赖一些库,而这些库又会依赖其他一些库,像这样依赖传递下去,使得依赖关系呈现指数级复杂关系,很容易出错。
    2、遮蔽(Shadowing)
    不同的jar包在类路径上的类有完全相同的名字。这种现象比较常见,原因有很多,比如相同库中有两个不同版本,库被重命名并添加到类路径中两次。
    由于类在第一个包含他们的JAR包中就已经被加载,类中的变量会“shadow” 所有其他变量,使得他们不能在用了。
    是不是说的比较晦涩?show me the code!!!

    public class Base {
        public  String name = "Base";
        public  String getName() { return name; }
    }
    
    
    public class Sub extends Base {
        public String name = "Sub";
        public String getName() { return name; }
    }
    

    下面程序输出的是“Sub”, 子类shadow 父类的变量,是不是立马理解了?

    class Program {
      public static void main(String[] args) {
        Sub s = new Sub();
        System.out.println(s.name); //Output "Sub"
      }
    }
    

    3、版本冲突(Version Conflicts)
    当两个库依赖的的第三方库是不同并且不兼容的时候就会出现版本问题。如果两个版本同时出现在类路径中,会出现不可预知的结果。首先因为“shadowing”,在这两个版本中存在的类只会加载一个。更糟糕的是,如果一个类存在于一个库而不在另一个中,这个类被访问的时候也会被加载进来,调用库的代码会发现两个版本混在一起。但是不兼容版本是必须的,缺少一个程序就无法正常运行,比如抛出NoClassDefFoundErrors
    4、复杂的类加载(Complex Class Loading)
    默认情况下所有程序类都是被相同的类加载器加载的,但是开发人员可以自己定义类加载器。类加载通常对开发者来说是不可见的,由容器比如web服务、组件系统来完成,但是
    all abstractions are leaky。开发人员在某些情况下得自己显示地加载类,这些自定义的类加载器会导致复杂的不可预知的行为。(原谅我直接这句话直接翻译了%>_<%)。

    那么Module是如何处理上述四个问题的呢?

    首先看一下JDK 9如何定义一个module:
    模块的是通过module-info.java进行定义,编译后打包后,就成为一个模块的实体;在模块的定义文件中,我们需要指定模块之间的依赖靠关系,可以exports给那些模块用,需要使用那些模块(requires) 。下面是一个例子:

    module com.foo.bar {
    requires org.baz.qux;
    exports com.foo.bar.alpha;
    exports com.foo.bar.beta;
    }
    

    1、对于第一个问题,先看下图,左边是定义module的代码,app需要zoop和sql,右边是依赖关系。

    image.png

    JVM会通过了module path 去解析所有的依赖,类似下面的图

    image.png

    ,这个过程发生在启动阶段,而不是运行阶段。当不是所有的modules在这条路径上被发现时,解析一个module传递依赖的过程就失败了。这就解决了无法描述和无限传递依赖的问题。
    2、第二问题。模块系统只要发现两个模块输出相同的包给同一个模块就会报错。如下图所示。

    image.png

    3、版本冲突问题,最直接的解决方式是模块系统能加载相同模块的不同版本,并且保证这些版本不会互相影响,保证封装性和可读性。但是抱歉,版本冲突问题目前没有办法解决。原因摘自官网:

    It is not necessary to support more than one version of a module within a single configuration.
    事实上,现在的Maven和Gradle也没法理解module的版本信息。

    4、最后是复杂的类加载问题,先说结果:Module并没有彻彻底底解决该问题,但是简化了问题复杂性。
    Oracle引入了层(Layer)的概念。(一个module够烦的,还抛出个layer概念,发现很多互联网公司都喜欢抛个新概念出来强行装逼啊!!!!!TMD)。layer控制模块和类加载器之间的关系。使得在同一个layer里的不同模块都使用相同的类加载器。换句话说类加载器和模块之间是1:n的关系。同时Layer还有堆叠的特点,如下图所示。

    image.png

    一个新layer可以在boot layer上构建,另外的layer也能在这个layer上构建。在解析某个layer里的module时候可以读取里面的modules或者在它下面的layer里的module。

    说白了,我的个人理解,layer就是让一个类加载器去专门负责某个module下的所有类加载,有什么好处,我也不知道啊?各位能不能写个列子出来???

    讲了模块化的好处,似乎还是比较抽象,下面两张图比较直观展示模块化之后的好处:
    下是 jdk 8中包的依赖关系

    image.png

    是不是很复杂呢?

    JAVA 9 引入模块后,将所有的类组织成模块形式,模块之间有着优美的依赖关系
    再看JDK9 简化后的关系图

    image.png

    最后的问题

    以下是Jigsaw官网的原话:

    Make it easier for developers to construct and maintain libraries and large applications, for both the Java SE and EE Platforms.

    是不是让你联想到了MavenGradle? 他们之间到底是什么关系啊??

    image.png

    Google 一下在StackOverFlow上找到了答案。

    在Jigsaw之后,public 关键字不再意味着任意的可访问性。访问的范围局限在JAR,想要访问 JAR包外的其他类,必须export。 任何模块之间的交互必须通过module-info文件来定义。
    举个列子,生成的WAR包可能并没改变源代码,但是在其中的所有JAR必须定义module-info。
    Maven 有两个主要的特征。他负责项目的依赖管理和构建。依赖管理的意思是Maven可以决定库中的版本并从仓库中下载下来。构建的意思是Maven可以编译代码并且打包成产品。

    Module是系统内置用于表述组件之间的关系,对于版本的管理还是处于最原始的状体。它管理一种强制的依赖关系。

    总结一下:Maven还是组要负责构建,包括开发过程中的各种任务,初始化,测试。但是要利用Jigsaw, Maven必须知道如何通过Jigsaw模块编译打包。Modules 对于像Maven这样的构建工具(build tools)来说扮演的是辅助补充的角色。因为这些构建工具在构建时建立的依赖关系图谱和他们版本可以根据Module来创建,Module强制确定了module和artifacts之间的依赖关系,而Maven对于依赖并非是强制的。

    本文主要参考了一下两个国外文献,一篇CSDN上的博客,以及oracle官方的视频讲解,youtube上有的(别问我怎么翻墙)
    https://dzone.com/articles/what-is-jar-hell
    https://blog.codefx.org/java/dev/will-there-be-module-hell/#Shadowing
    http://www.cnblogs.com/xiongxx/p/6734416.html


    感谢阅读,这是我第一次在简书上写文章,以后会多写一些,有问题欢迎讨论,最后祝大家节日快乐!!

    相关文章

      网友评论

        本文标题:java 9 功能的新特性 (一)模块化

        本文链接:https://www.haomeiwen.com/subject/rhgqyxtx.html