什么是模块化编程
什么是模块化编程?模块化编程是将原有的系统分解为若干个自己管理的模块,但是这些模块之间又是互相通信(连接)的。模块,或者可以称之为组件,也就成了一个个可以识别的独立物件,它们可以包括代码、元数据描述,以及和其他模块之间的关系等。理想地看,这些物件从编译时期开始就是可以被识别的,生命周期贯穿整个运行时。这样也就可以想象了,我们的应用程序在运行时应该是由多个模块组成的。
作为一个Java模块,必须满足三个基本要求:
1. 强封装性
对于封装的重要性应该不用和大家解释了,两个模块之间仅需要知道对方的封装接口、参数、返回值,而对于它内部的实现细节,其他调用方并不关心,内部怎么变化都没关系,只要能够继续调用并返回正确的值就行。
2. 定义良好的接口
这里包含两层意思。一是模块之间的边界要划分清楚,不能存在重复的部分,二是对于无法封装的公开代码,如果进行了破坏性的修改,那么其它调用方来说也是破坏性的,因此需要提供定义良好并且稳定的接口给其他调用模块调用。
3. 显式依赖
这是点和面的关系。每一个点代表一个模块,两点之间的线代表模块之间的依赖关系,所有点就组成了模块调用关系图。只有拥有清晰的模块调用关系图,我们才能确保调用关系的正确性和模块配置的可用性。Java9之前,我们可以采用maven来帮助管理外部依赖关系。
模块化带来的是灵活、可理解、可重用,这三大优点。模块化编程其实和当今很多软件架构概念是类同的,都是为了解决相似的抽象层问题,例如基于组件的开发、面向服务系统架构,或者更新的微服务架构。
前面提到了三个基本要求,强封装性、定义良好的接口、显式依赖,其实在Java9之前就已经支持了。比如封装,类型的封装可以通过使用包和访问修饰符(例如public、protected、private)的组合方式完成。例如protected,只有在同一个包内的类才能访问protected类里面的方法。这里就可以提出一个问题来了。如果我们想要让一些包外的类可以访问protected类,又不想让另外一些包外的类可以访问,这时候应该怎么处理呢?Java9之前没有很好的解决方案。对于第二个要求,定义良好的接口,这一点Java语言一直做得不错,从一开始就做得不错。你会发现接口方式在整个模块化编程中扮演了中心角色。对于显式依赖,由于Java提供的import关键字所引入的jar包需要在编译时才会真正加载,当你把代码打入jar包的时候,你并不知道哪一个JAR文件包含了你的JAR包需要运行的类型。为了解决这个问题,我们可以利用一些外部工具,例如Maven、OSGi。Java9虽然从jvm核心层和语言层解决了依赖控制问题,但是Maven、OSGi还是有用武之地的,它们可以基于Java模块化编程平台之上继续持续自己的依赖管理工作。
图1 应用程序的Jar包关系图
上面这张图包含了两个部分,一部分是应用程序,包含为Application.jar的应用程序JAR包、该Jar包的两个依赖库(Google Guava和Hibernate Validator),以及三个外部依赖Jar包。我们可以通过maven工具完成库之间的依赖关系绑定功能。Java9出现之前,Java运行时还需要包含rt.jar,如上图所示。从这张图上至少可以看出没有强有力的封装概念,为什么这么说?以Guava库为例,它内部的一些类是真的需要被Application.jar工程使用的,但是有一些类是不需要被使用的,但是由于这些类的访问限制符也是public的,所以外部包里的类其实是可以访问到的,所以说没有履行Java9的封装要求。
大家知道,当JVM开始加载类时,采用的方式是顺序读取classpath里面设置的类名并找到需要的类,一旦找到了正确的类,检索工作结束,转入加载类过程。那么如果classpath里面没有需要的类呢?那就会抛出运行时错误(run-time exception)。又由于JVM采用的延迟加载方式(lazy loading),因此极有可能某个用户点了某个按钮,然后就奔溃了,这是因为JVM不会从一开始就有效地验证classpath的完整性。那么,如果classpath里面存在重复的类,会出现什么情况呢?可能会出现很多莫名其妙的错误,例如类的方法找不到,这有可能是因为配置了两个不同版本的jar包。
发个小广告!!!走过路过,不要错过!新书来啦!!!
注:本公众号与当当店铺并无从属关系,仅为大家提供一个便捷购物地址。若有所冲突,纯属巧合,立删。
麦克叔叔每晚十点说
领取专属 10元无门槛券
私享最新 技术干货