重學(xué)Java編程第十天—LXF的教程(classpath 和jar&模塊)

classpath 和 jar

在Java中精钮,關(guān)于什么是classpath.classpath是JVM用到的一個(gè)環(huán)境變量,它用來(lái)指示JVM如何搜索class.
因?yàn)镴ava是編譯型語(yǔ)言爽待,源碼文件是.java,而編譯后的.class文件才是真正可以被JVM執(zhí)行的字節(jié)碼腐魂。因此咧最,JVM需要知道捂人,如何要加載一個(gè)abc.xyz.Hello的類,應(yīng)該去哪搜索對(duì)應(yīng)的Hello.class文件矢沿。
所以先慷,classpath就是一組目錄的集合,它設(shè)置的搜索路徑與操作系統(tǒng)相關(guān)咨察。例如论熙,在Windows系統(tǒng)上,用;分隔摄狱,帶空格的目錄用""括起來(lái)脓诡,可能長(zhǎng)這樣:

C:\work\project1\bin;C:\shared;"D:\My Documents\project1\bin"

在Linux系統(tǒng)上,用:分隔媒役,可能長(zhǎng)這樣:

/usr/shared:/usr/local/bin:/home/liaoxuefeng/bin

現(xiàn)在我們假設(shè)classpath.;C\work\project1\bin;C:\shared,當(dāng)JVM加載abc.xyz.Hello這個(gè)類時(shí)祝谚,會(huì)依次查找:

  • <當(dāng)前目錄>\abc\xyz\Hello.class

  • C:\work\project1\bin\abc\xyz\Hello.class

  • C:\shared\abc\xyz\Hello.class
    注意到.代表當(dāng)前目錄。如果JVM在某個(gè)路徑下找到了對(duì)應(yīng)的class文件酣衷,就不再往后繼續(xù)搜索交惯。如果所有路徑下都沒(méi)有找到,就報(bào)錯(cuò)穿仪。
    classpath的設(shè)定方法有兩種:
    在系統(tǒng)環(huán)境變量中設(shè)置classpath環(huán)境變量席爽,不推薦;
    在啟動(dòng)JVM時(shí)設(shè)置classpath變量啊片,推薦只锻。
    強(qiáng)烈不推薦在系統(tǒng)環(huán)境變量中設(shè)置classpath,那樣會(huì)污染整個(gè)系統(tǒng)環(huán)境紫谷。在啟動(dòng)JVM時(shí)設(shè)置classpath才是推薦的做法齐饮。實(shí)際上就是給java命令傳入-classpath-cp參數(shù):

java -classpath .;C:\work\project1\bin;C:\shared abc.xyz.Hello

或者使用-cp的簡(jiǎn)寫:

java -cp .;C:\work\project1\bin;C:\shared abc.xyz.Hello

沒(méi)有設(shè)置系統(tǒng)環(huán)境變量捐寥,也沒(méi)有傳入-cp參數(shù),那么JVM默認(rèn)的classpath.,即當(dāng)前目錄:

java abc.xyz.Hello

上述命令告訴JVM只在當(dāng)前目錄搜索Hello.class祖驱。
在IDE中運(yùn)行Java程序握恳,IDE自動(dòng)傳入的-cp參數(shù)是當(dāng)前工程的bin目錄和引入的jar包。
通常捺僻,我們?cè)谧约壕帉懙?code>class中睡互,會(huì)引用Java核心庫(kù)的class,例如陵像,StringArrayList等寇壳。這些class應(yīng)該上哪去找醒颖?
有很多"如何設(shè)置classpath"的文章會(huì)說(shuō)明把JVM自帶的rt.jar放入classpath,但事實(shí)上,根本不需要告訴JVM如何去Java核心庫(kù)查找class壳炎。

image.png

更好的做法是泞歉,不要設(shè)置classpath!默認(rèn)的當(dāng)前目錄.對(duì)于絕大多數(shù)情況都?jí)蛴昧恕?br> 假設(shè)我們有一個(gè)編譯后的Hello.class,它的包名是com.example匿辩,當(dāng)前目錄是C:\work,那么腰耙,目錄結(jié)構(gòu)必須如下:
image.png

運(yùn)行這個(gè)Hello.class 必須在當(dāng)前目錄下使用如下命令:

C:\work> java -cp . com.example.Hello

JVM根據(jù)classpath設(shè)置的.在當(dāng)前目錄下查找com.example.Hello,即實(shí)際搜索文件必須位于com/example/Hello.class铲球。如果指定的.class文件不存在挺庞,或者目錄結(jié)構(gòu)和包名對(duì)不上,均會(huì)報(bào)錯(cuò)稼病。

jar 包

如果有很多的.class文件选侨,散落在各層目錄中,不便于管理然走。如果能把目錄打一個(gè)包援制,變成一個(gè)文件,就方便多了芍瑞。
jar包就是用來(lái)干這個(gè)事的晨仑,它可以把package組織的目錄層級(jí),以及各個(gè)目錄下的所有的文件(包括.class文件和其他文件)都打成一個(gè)jar文件拆檬,這樣一來(lái)洪己,無(wú)論是備份,還是發(fā)給客戶竟贯,就簡(jiǎn)單多了码泛。
jar 包實(shí)際上就是一個(gè)zip格式的壓縮文件,而jar包相當(dāng)于目錄澄耍。如果我們要執(zhí)行一個(gè)jar包的class,就可以把jar包放到classpath中:

java -cp ./hello.jar abc.xyz.Hello

這樣JVM會(huì)自動(dòng)在hello.jar文件里去搜索某個(gè)類噪珊。
那么問(wèn)題來(lái)了:如何創(chuàng)建jar包晌缘?
因?yàn)閖ar包就是zip包,所以痢站,直接在資源管理器中磷箕,找到正確的目錄,點(diǎn)擊右鍵阵难,在彈出的快捷菜單中選擇“發(fā)送到”,“壓縮(Zipped)文件夾”,就制作了一個(gè)zip文件岳枷。然后,把后綴從.zip改為.jar呜叫,一個(gè)jar包就創(chuàng)建成功空繁。
假設(shè)編譯輸出的目錄結(jié)構(gòu)就是這樣:

image.png

這里需要特別注意的是,jar包里的第一層目錄朱庆,不能是bin,而應(yīng)該是hong,ming,mr盛泡。如果在Windows的資源管理器中看,應(yīng)該長(zhǎng)這樣:
image.png

說(shuō)明打包打得有問(wèn)題娱颊,JVM仍然無(wú)法從jar包中查找正確class傲诵,原因是hong.Person必須按hong/Person.class存放,而不是bin/hong/Person.class 箱硕。
jar包還可以包含一個(gè)特殊的/META-INF/MANIFEST.MF文件拴竹,MANIFEST.MF是純文本,可以指定Main-Class和其它信息剧罩。JVM會(huì)自動(dòng)讀取這個(gè)MANIFEST.MF文件栓拜,如果存在Main-Class,我們就不必在命令行指定啟動(dòng)的類名,而是更方便的命令:

java -jar hello.jar

jar包還可以包含其它jar包惠昔,這個(gè)時(shí)候菱属,就需要在MANIFEST.MF文件里配置classpath了。
在大型項(xiàng)目中舰罚,不可能手動(dòng)編寫MANIFEST.MF文件纽门,再手動(dòng)創(chuàng)建zip包。Java社區(qū)提供了大量的開(kāi)源構(gòu)建工具营罢,例如Maven,可以非常方便地創(chuàng)建jar包赏陵。
小結(jié):
JVM通過(guò)環(huán)境變量classpath決定搜索class的路徑和順序。
不推薦設(shè)置系統(tǒng)環(huán)境變量classpath饲漾,始終建議通過(guò)-cp命令傳入蝙搔;
jar包相當(dāng)于目錄,可以包含很多.class文件考传,方便下載和使用吃型;
MANIFEST.MF文件可以提供jar包的信息,如Main-Class僚楞,這樣可以直接運(yùn)行jar包

模塊

從Java 9 開(kāi)始勤晚,JDK又引入了模塊(module)
.class文件是JVM看到的最小可執(zhí)行文件枉层,而一個(gè)大型程序需要編寫很多Class,并生成一堆.class文件赐写,很不便于管理鸟蜡,所以,jar文件就是class文件的容器挺邀。
在Java 9之前揉忘,一個(gè)大型Java程序會(huì)生成自己的jar文件,同時(shí)引用依賴的第三方j(luò)ar文件端铛,而JVM自帶的標(biāo)準(zhǔn)庫(kù)泣矛,實(shí)際上也是以jar文件形式存放的,這個(gè)文件叫rt.jar禾蚕,一共有60多M您朽。
如果是自己開(kāi)發(fā)的程序,除了一個(gè)自己的app.jar以外夕膀,還需要一堆第三方的jar包,運(yùn)行一個(gè)Java程序美侦,一般來(lái)說(shuō)产舞,命令行寫這樣:

java -cp app.jar:a.jar:b.jar:c.jar com.liaoxuefeng.sample.Main

image.png

如果漏寫了某個(gè)運(yùn)行時(shí)需要用到的jar,那么在運(yùn)行時(shí)期極有可能拋出ClassNotFoundException
所以菠剩,jar只是用于存放class的容器易猫,它并不關(guān)心class之間的依賴。
從Java 9 開(kāi)始引入的模塊具壮,主要是為了解決“依賴”這個(gè)問(wèn)題准颓。如果a.jar必須依賴另一個(gè)b.jar才能運(yùn)行,那我們應(yīng)該給a.jar加點(diǎn)說(shuō)明棺妓,使得程序在編譯和運(yùn)行的時(shí)候能自動(dòng)定位到b.jar攘已,這種自帶“依賴關(guān)系”的class容器就是模塊。
為了表明Java模塊化的決心怜跑,從Java 9開(kāi)始样勃,原有的Java標(biāo)準(zhǔn)庫(kù)已經(jīng)由一個(gè)單一巨大的rt.jar分拆成了幾十個(gè)模塊,這些模塊以.jmod擴(kuò)展標(biāo)識(shí)性芬,可以在$JAVA_HOME/jmods目錄下找到它們:

  • java.base.jmod
  • java.compiler.jmod
  • java.datatransfer.jmod
  • java.desktop.jmod
  • ...
    這些.jmod文件每一個(gè)都是一個(gè)模塊峡眶,模塊名就是文件名。例如:模塊java.base對(duì)應(yīng)的文件就是java.base.jmod植锉。模塊之間的依賴關(guān)系已經(jīng)被寫入到模塊內(nèi)的module_info.class文件了辫樱。所有的模塊都直接或間接地依賴java.base模塊,只有java.base模塊不依賴任何模塊俊庇,它可以被看作是“很模塊”狮暑,好比所有的類都是從Object直接或間接繼承而來(lái)鸡挠。

把一堆class封裝為jar僅僅是一個(gè)打包的過(guò)程,而把一堆class封裝為模塊則不但需要打包心例,還需要寫入依賴關(guān)系宵凌,并且還可以包含二進(jìn)制代碼(通過(guò)JNI擴(kuò)展).此外,模塊支持多版本止后,即在同一個(gè)模塊中可以為不同的JVM提供不同的版本瞎惫。

編寫模塊

首先,創(chuàng)建模塊和原有的創(chuàng)建Java項(xiàng)目是完全一樣的译株,以oop-module工程為例瓜喇,它的目錄結(jié)構(gòu)如下:

image.png

其中,bin目錄存放編譯后的class文件歉糜,src目錄存放源碼乘寒,按包名的目錄結(jié)構(gòu)存放,僅僅在src目錄下多了一個(gè)module-info.java這個(gè)文件匪补,這就是模塊的描述文件.在這個(gè)模塊中伞辛,它長(zhǎng)這樣:

module hello.world {
    requires java.base; // 可不寫,任何模塊都會(huì)自動(dòng)引入java.base
    requires java.xml;
}

其中夯缺,module是關(guān)鍵字蚤氏,后面的hello.world是模塊名稱,它的命名規(guī)范與包一致踊兜「捅酰花括號(hào)的requires xxx;表示這個(gè)模塊需要引用的其它模塊名。除了java.base可以被自動(dòng)引入外捏境,這里我們引入了一個(gè)java.xml模塊于游。

當(dāng)我們使用模塊聲明了依賴關(guān)系后,才能使用引入的模塊垫言。例如贰剥,Main.java代碼如下:

package com.itranswarp.sample;

// 必須引入java.xml模塊后才能使用其中的類:
import javax.xml.XMLConstants;

public class Main {
    public static void main(String[] args) {
        Greeting g = new Greeting();
        System.out.println(g.hello(XMLConstants.XML_NS_PREFIX));
    }
}

如果把requires java.xml;module-info.java中去掉,編譯將報(bào)錯(cuò)筷频○海可見(jiàn),模塊的重要作用就是聲明依賴關(guān)系截驮。
下面笑陈,我們用JDK提供的命令行工具來(lái)編譯創(chuàng)建模塊。
首先葵袭,我們把工作目錄切換到oop-module,在當(dāng)前目錄下編譯所有的.java文件涵妥,并存放到bin目錄下,命令如下:

$ javac -d bin src/module-info.java src/com/itranswarp/sample/*.java

如果編譯成功∑挛現(xiàn)在項(xiàng)目結(jié)構(gòu)如下:

image.png

注意到src目錄下的module-info.java被編譯到bin目錄下的module-info.class蓬网。
下一步窒所,我們需要把bin目錄下的所有class文件先打包成jar,在打包的時(shí)候,注意傳入--main-class參數(shù),讓這個(gè)jar包能自己定位main方法所在的類:

$ jar --create --file hello.jar --main-class com.itranswarp.sample.Main -C bin .

現(xiàn)在我們就在當(dāng)前目錄下得到了hello.jar這個(gè)jar包帆锋,它和普通jar包并無(wú)區(qū)別吵取,可以直接使用命令java -jar hello.jar來(lái)運(yùn)行它。但是我們的目標(biāo)是創(chuàng)建模塊锯厢,所以皮官,繼續(xù)使用JDK自帶的jmod命令把一個(gè)jar包轉(zhuǎn)換成模塊:

$ jmod create --class-path hello.jar hello.jmod

于是,在當(dāng)前目錄下我們又得到了hello.jmod這個(gè)模塊文件实辑,這就是最后打包出來(lái)的傳說(shuō)中的模塊捺氢!

運(yùn)行模塊

要運(yùn)行一個(gè)jar,我們使用java -jar xxx.jar命令。要運(yùn)行一個(gè)模塊剪撬,我們只需要指定模塊名摄乒。試試:

$ java --module-path hello.jmod --module hello.world

結(jié)果是一個(gè)錯(cuò)誤:

Error occurred during initialization of boot layer
java.lang.module.FindException: JMOD format not supported at execution time: hello.jmod

原因是.jmod不能被放入--module-path中。換成.jar就沒(méi)問(wèn)題了:

$ java --module-path hello.jar --module hello.world
Hello, xml!

那我們辛辛苦苦創(chuàng)建的hello.jmod有什么用残黑?答案是我們可以用它來(lái)打包JRE

打包JRE

為了支持模塊化馍佑,Java 9 首先帶頭把自己的一個(gè)巨大無(wú)比的rt.jar拆成了幾十個(gè).jmod模塊,原因就是梨水,運(yùn)行Java程序的時(shí)候拭荤,實(shí)際上我們用到的JDK模塊,并沒(méi)有那么多冰木。不需要的模塊穷劈,完全可以刪除笼恰。
過(guò)去發(fā)布一個(gè)Java應(yīng)用程序踊沸,要運(yùn)行它,必須下載一個(gè)完整的JRE社证,再運(yùn)行jar包逼龟。而完整的JRE塊頭很大,有100多M追葡。怎么給JRE瘦身呢腺律?

現(xiàn)在,JRE自身的標(biāo)準(zhǔn)庫(kù)已經(jīng)分拆成了模塊宜肉,只需要帶上程序用到的模塊匀钧,其他的模塊就可以被裁剪掉。怎么裁剪JRE呢谬返?并不是說(shuō)把系統(tǒng)安裝的JRE給刪掉部分模塊之斯,而是“復(fù)制”一份JRE,但只帶上用到的模塊遣铝。為此佑刷,JDK提供了jlink命令來(lái)干這件事莉擒。命令如下:

$ jlink --module-path hello.jmod --add-modules java.base,java.xml,hello.world --output jre/

我們?cè)?code>--module-path參數(shù)指定了我們自己的模塊hello.jmod,然后瘫絮,在--add-modules參數(shù)中指定了我們用到的3個(gè)模塊java.base涨冀、java.xmlhello.world,用,分隔麦萤。最后鹿鳖,在--output參數(shù)指定輸出目錄。
現(xiàn)在频鉴,在當(dāng)前目錄下栓辜,我們可以找到jre目錄,這是一個(gè)完整的并且?guī)в形覀冏约?code>hello.jmod模塊的JRE垛孔。試試直接運(yùn)行這個(gè)JRE:

$ jre/bin/java --module hello.world
Hello, xml!

要分發(fā)我們自己的Java應(yīng)用程序藕甩,只需要把這個(gè)jre目錄打個(gè)包給對(duì)方發(fā)過(guò)去,對(duì)方直接運(yùn)行上述命令即可周荐,既不用下載安裝JDK狭莱,也不用知道如何配置我們自己的模塊,極大地方便了分發(fā)和部署概作。

訪問(wèn)權(quán)限

前面講過(guò)腋妙,Java的class訪問(wèn)權(quán)限分為public、protected讯榕、private和默認(rèn)的包訪問(wèn)權(quán)限骤素。引入模塊后,這些訪問(wèn)權(quán)限的規(guī)則就要稍微做些調(diào)整愚屁。

確切地說(shuō)济竹,class的這些訪問(wèn)權(quán)限只在一個(gè)模塊內(nèi)有效,模塊和模塊之間霎槐,例如送浊,a模塊要訪問(wèn)b模塊的某個(gè)class,必要條件是b模塊明確地導(dǎo)出了可以訪問(wèn)的包丘跌。
舉個(gè)例子:我們編寫的模塊hello.world用到了模塊java.xml的一個(gè)類javax.xml.XMLConstants袭景,我們之所以能直接使用這個(gè)類,是因?yàn)槟Kjava.xmlmodule-info.java中聲明了若干導(dǎo)出:

module java.xml {
    exports java.xml;
    exports javax.xml.catalog;
    exports javax.xml.datatype;
    ...
}

只有它聲明的導(dǎo)出的包闭树,外部代碼才被允許訪問(wèn)耸棒。換句話說(shuō),如果外部代碼想要訪問(wèn)我們的hello.world模塊中的com.itranswarp.sample.Greeting類报辱,我們必須將其導(dǎo)出:

module hello.world {
    exports com.itranswarp.sample;

    requires java.base;
    requires java.xml;
}

因此与殃,模塊進(jìn)一步隔離了代碼的權(quán)限。


小結(jié)

Java 9引入的模塊目的是為了管理依賴;
使用模塊可以按需打包JRE奈籽;
使用模塊對(duì)類的訪問(wèn)權(quán)限有了進(jìn)一步限制饥侵。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市衣屏,隨后出現(xiàn)的幾起案子躏升,更是在濱河造成了極大的恐慌,老刑警劉巖狼忱,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件膨疏,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡钻弄,警方通過(guò)查閱死者的電腦和手機(jī)佃却,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)窘俺,“玉大人饲帅,你說(shuō)我怎么就攤上這事×隼幔” “怎么了灶泵?”我有些...
    開(kāi)封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)对途。 經(jīng)常有香客問(wèn)我赦邻,道長(zhǎng),這世上最難降的妖魔是什么实檀? 我笑而不...
    開(kāi)封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任惶洲,我火速辦了婚禮,結(jié)果婚禮上膳犹,老公的妹妹穿的比我還像新娘恬吕。我一直安慰自己,他們只是感情好镣奋,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布币呵。 她就那樣靜靜地躺著怀愧,像睡著了一般侨颈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芯义,一...
    開(kāi)封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天哈垢,我揣著相機(jī)與錄音,去河邊找鬼扛拨。 笑死耘分,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播求泰,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼央渣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了渴频?” 一聲冷哼從身側(cè)響起芽丹,我...
    開(kāi)封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎卜朗,沒(méi)想到半個(gè)月后拔第,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡场钉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年蚊俺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逛万。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡泳猬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宇植,到底是詐尸還是另有隱情暂殖,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布当纱,位于F島的核電站呛每,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏坡氯。R本人自食惡果不足惜晨横,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望箫柳。 院中可真熱鬧手形,春花似錦、人聲如沸悯恍。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)涮毫。三九已至瞬欧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間罢防,已是汗流浹背艘虎。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工忌怎, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留油航,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓确买,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親候生。 傳聞我的和親對(duì)象是個(gè)殘疾皇子同眯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容