简单说说 java中的 模块系统

java中一般的包和类的封装系统已经不能满足我们对访问的控制。

因此有了Java9有了模块系统,有以下2个优点:

1.强封装性:我们可以控制哪些包是可访问的,并且无需区维护那些我们不想公开给大众的代码

2.可靠的配置:我们可以避免类重复或类丢失,这类常见的路径问题

对模块命名

模块是包的集合 ,并且模块名可以和包名重复。

模块名只模块声明中。在java类源文件中,永远不应该引用模块名

使用模块

模块文件在项目的根目录,模块文件叫 **module-info.java **。

如果我们编写这么一个类文件:

package mo_kuai;

import javax.swing.JOptionPane;
public class test1 {
    public static void main(String[] args) {
        JOptionPane.showMessageDialog(null, "Hello java");
        //这个目的是用一个JOptionPane对象展示Hello java消息
    }
}

这时,如果 module-info.java 为:

module mo_kuai {
}

mo_kuai模块里没有引入其他模块。那么编译会报错。因为javax.swing包在java.desktop模块中。我们的模块需要声明它依赖这个模块:

module mo_kuai {
    requires java.desktop;
}

用requires 来需求模块。这样你的模块就可以用这个模块了;

注意:java.lang和java.io是默认需求在模块中的

我们的 mo_kuai模块只列出了自己的模块需求,它需要java.desktop模块,这样我们才能用javax.swing包。而java.desktop模块自身声明了3个它需要的模块。即java.datatransfer,java.prefs和java.xml

下面这个图就表示了模块之间的需求关系:

 

要注意的是:

  • 模块中不能有环,即一个模块不能直接或间接的对自己产生依赖
  • 模块不会自动把访问权限传递给其他模块,如上面的例子:mo_kuai模块需求java.desktop模块,java.desktop模块需求java.xml模块,这不会给予mo_kuai模块访问java.xml模块中包的权力。即按数学的术语来说,require不会是传递性的。 这使得需求必须明确化,是我们想要的行为

导出包

我们可以看到一个模块如果想要用其他模块中的包,就必须声明该模块。但并不会自动使所需模块中的所有包都可用。

模块可以用exports关键词来声明哪些包可用,如

module mo_kuai {
    requires java.desktop;
    exports 包1;
    exports 包2;
    .....
}

这个模块可以让导出的包可用,通过不导出的其他包来隐藏他们。没有导出的包在其自己的模块之外是不可访问的。

警告:模块没有作用域的概念,不能在不同的模块中放置2个相同名字的包。即使是隐藏的包(即没有导出的包)

模块和反射式访问

按照纯理论来说,破坏对象的封装并窥视其私有的成员是错误的。但是像对象-关系映射和XML/Json绑定这样的机制应用的十分广泛,使得模块系统也必须接纳 这种行为。

通过opens关键词,模块就可以打开包,从而启动对给定包的类的所有实例进行放射式访问。
module mo_kuai {
    requires java.desktop;
  opens 包;//声明自己模块的这个包 为open的
}

也可以像这样声明 Open:

open module mo_kuai {//这样就声明这个模块为开放模块
    requires java.desktop;
}

开放的模块可以授权对所有的包的运行时访问,就像所有的包都用exports和opens声明过一样,但是,在运行时只有显式导出的包是可访问的。开发模块将模块系统编译时的安全性和经典的授权许可的运行时行为结合在一起。

JAR文件中除了类文件和清单外,还可以包含文件资源文件,它可以被Class.getResourceAsStream方法加载,现在还可以被Module.getResourceAsStream方法加载。如果资源文件在某个包里,那么这个包必须对调用者是开放的

自动模块

如果JAR文件没有进行模块化,那么我们有两种机制来完成 未模块化的JAR包与模块化的项目的适配:自动化模块和不具名模块

我们可以通过把任何JAR文件放置于模块路径的目录而不是类路径的目录中,实现将其转换为一个模块。模块路径上没有modul-info.class文件的JAR被称为自动模块。

  1. 模块隐式对其他所有模块 requires
  2. 所有的包都被导出且是开放的
  3. 如果JAR文件清单META-INF/MANIFEST.MF中具有 建为Automatic-Module-Name的项,那么它的值就会变成模块名
  4. 否则模块名从JAR的文件名获得,将文件尾部的版本号删除,将非字母 数字的字符都替换为.句点

如一个JAR包叫 jk-dk-1.5.jar 那么这个模块名叫 jk.dk

在将jar包放在模块路径前,请检查这个jar是否是已经模块化的

不具名模块

任何不在模块路径的类都是不具名模块的一部分。从计算上来说,可能有多个不具名的模块,但他们合起来就像是单个不具名模块。

与自动模块一样,不具名模块可以访问其他的模块,他们的所有包都会被导出,并且都是开放的。

但是,没有任何**明确模块**可以访问不具名模块(明确模块是指不是自动模块或不具名模块的模块)。换句话来说明确模块总是可以避免类路径的坑。

自动模块可以读取不具名模块,因此他们的依赖关系放在类路径中


传递的需求和静态的需求

之前说到模块之间的需求是不具有传递性的,但这样也会带来很多麻烦,比如声明所有需要的模块会十分的冗长。所以现在你可以这样做:

module mo_kuai {
    requires transitive 模块1;
}
requires transitive 就是传递性需求。在这个例子中,你在mo_kuai模块中需求模块1,如果模块1也需求其他模块的话,你就不用再在mo_kuai中在声明需求 模块1需求的模块,它自动就帮你完成了。  这听起来有点绕。

我们回想起之前讲java.desktop模块时,我们说过java.desktop模块需求着其他3个模块,但我们事件上在我们自己的模块中只声明java.desktop这1个模块就可以使用java.desktop模块了。这是为什么呢?  因为在java.desktop模块中用的就是requires transitive来需求其他3个模块。这样我们自己的模块就相当于需求了这4个模块。
module java.desktop {
    requires java.prefs;
    requires transitive java.datatransfer;
    requires transitive java.xml;
    .....
  }
还有一种不常见的 requires static声明方式。它声明一个模块必须在编译时出现,而在运行时是可选的。可用于访问位于不同模块的编译时注解。

限定导出和开放

限定导出就是 限定你导出的这个包只能由你指定的模块访问:

module test {
    exports 包 to 模块1,模块2....;
  }//限定导出

同理限定开放 就是 限定你导出的这个包只能由你指定的模块 通过反射访问私有数据:

module test {
    opens 包 to 模块1,模块2....;
  }//限定开放

服务加载器

我们先介绍一下服务加载器,java的jdk中带有简易的服务加载器类。用法是通过指定的 接口的Class对象,得到实现了该接口的实现类的对象。

这里给出一个例子:

public interface hello {
     String hello();
}//这是hello接口
------------------------------------------
public class hello_1 implements hello{
    @Override
    public String hello() {
        return "这是hello_1的问候";
    }
}//这是一个hello接口的实现类
---------------------------------
public class hello_2 implements hello{
    @Override
    public String hello() {
        return "这是hello_2的问候";
    }
}
//这是另一个hello接口的实现类
----------------------------------------
public static void main(String[] args) {
  //通过ServiceLoader类static的load(Class对象)方法得到一个 指定接口的
  //实现类的对象合集,这要要求你的实现类有public的无参构造方法
        ServiceLoader<hello> helloLoader=ServiceLoader.load(hello.class);
        for(var i:helloLoader) {
          //调用每个实现类对象的实现的hello方法
            System.out.println(i.hello());
        }
}//这是测试代码
----------------------------
//最终输出:
"这是hello_1的问候
这是hello_2的问候"

在过去实现类是放在jar包里的META-INF/services目录中提供的,

但现在可用模块声明这种实现类:

module npc {
    exports npc;
    provides npc.hello with npc.hello_1,npc.hello_2; 
}//这样就相当于提供了hello接口npc.hello_1,npc.hello_2实现类
module mo_kuai {
    requires npc;
    uses npc.hello;
}//提供uses 接口 这样的格式来使用

provides和uses声明的效果,使得消费该服务的模块允许访问私有实现类

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值