理解classpath


一、什么是classpath


  classpath,翻譯過來就是類路徑的意思,它是包含class文件的路徑集合,用於指示虛擬機jvm在這些路徑下搜索class文件。
  類路徑可以同時定義多個,多個類路徑之間需要使用分隔符進行分隔,windows環境下使用“;”,linux環境下則使用“:”。下面我們對類路徑進行一個簡單的分類,如下所示:
類路徑的類型 描述
"." 表示當前目錄,即執行java命令啟動應用的目錄。如果我們不顯式的設置任何類路徑,則默認它包含一個多當前目錄;但如果顯式設置了類路徑,並且設置的路徑不包含當前目錄的話,則類路徑就不包含當前目錄了
擴展目錄 (1)擴展目錄可以是包含class文件的基目錄,比如“/home/user/classdir”,該目錄作為類路徑,其下的內容就開始是包路徑目錄。
(2)擴展目錄也可以是一些其他的文件目錄,不過jvm並不能從中找到class文件,該目錄用於存放一些文件以供查找使用。
jar文件 比如“/home/user/lib/Foo.jar”,該jar文件的完整路徑也能作為類路徑。當然,從javaSE6開始,可以通過通配符“*”一次指定多個jar,例如“/home/user/lib/*”
運行時庫文件,rt.jar,jre/lib,jre/lib/ext 這些不用手動指定,它們默認包含在類路徑中
 

二、如何設置classpath


  啟動應用時,設置classpath非常簡單:
1 #方式1
2 java -classpath 具體的類路徑
3 #-classpath也可以簡寫成-cp
4 java -cp 具體的類路徑
5 #示例
6 java -classpath .:/home/user/classdir:/home/user/lib/*
7 
8 #方式2(不推薦使用),使用CLASSPATH環境變量,JVM啟動時會去查找CLASSPATH這個環境變量
9 export CLASSPATH=".:/home/user/classdir:/home/user/lib/*"

  需要注意的是,某些情況下不需要我們設置classpath,比如在tomcat或者jetty中啟動應用,亦或者是通過springboot可執行jar啟動應用時,我們都未設置classpath,但這並不代表不用設置,而是框架或者容器替我們做好了這個事。

 

三、"classpath:"與"classpath*:"


  這是一個擴展話題,我們在spring下常常能看到使用"classpath:"或者"classpath*:"去指定一個資源文件的路徑,這兩個路徑前綴是啥意思呢?
  其實"classpath:"或者"classpath*:"都不是java語言自帶的東西,它只是spring自定義的一種路徑格式前綴而已,意思是以classpath作為根目錄的指定位置。舉個例子,若應用啟動時指定了classpath為"/home/user/classdir",那么對於以"classpath:"開頭的路徑"classpath:resources/myfile.xml",spring就會在"/home/user/classdir/"下去查找相對路徑為myfile.xml的文件,也就是查找絕對路徑為"/home/user/classdir/myfile.xml"的文件。
  需要說明的是,"classpath:/"與"classpath:"對於spring來說是沒有區別的,spring框架在處理這個路徑的時候,會將開頭的"/"符號去掉,這點對於"classpath*:"也是一樣,具體可以查看PathMatchingResourcePatternResolver的getResources方法源碼。
  最后,我們需要搞清楚"classpath:"與"classpath*:"的區別,兩者之間相差一個"*"號,但在查找方式上具有很大的差異,這一點網上大部分博客都是你抄我或者我抄你,要么就沒說清楚,要么就直接有錯誤。

3.1.不包含通配符的路徑

  所謂不包含通配符的路徑,指的是"classpath:"以及"classpath*:"后面資源文件的路徑(含文件名)不包含通配符"*、?"等,因為spring在進一步處理路徑時,首先會判斷路徑是否包含通配符,有和沒有通配符的處理方式是完全不同的,這也是我們分開討論的原因。
  查找規則 進一步說明
classpath: (1)既可以查找類路徑中擴展目錄下的資源,也可以查找類路徑中jar包內的資源。
(2)查找順序是按照類路徑定義的順序逐個查找擴展目錄或jar包(並不是網上說的先去查找擴展目錄再查找jar包),並返回查找到的第一個資源。
(1)返回的Resource都是ClasspathResource。
(2)如果資源位於擴展目錄中,從ClasspathResource中獲取到的是BufferedInputStream;如果位於jar包中,獲取到的則是JarURLInputStream。
classpath*: (1)既可以查找類路徑中擴展目錄下的資源,也可以查找類路徑中jar包內的資源。
(2)返回查找到的所有的匹配文件資源,因此可以不考慮查找順序。
(1)返回的Resource都是URLResource。
(2)如果資源位於擴展目錄中,從URLResource獲取到的是BufferedInputStream;如果位於jar包中,獲取到的則是JarURLInputStream。
 

3.2.包含通配符的路徑

  包含通配符的路徑指的是"classpath:"以及"classpath*:"后面資源文件的路徑(含文件名)包含通配符"*、?"等。這種情況比較復雜,對於classpath尤其如此。因為spring在查找包含通配符的路徑時,首先會從路徑中提取出一個不包含通配符的“根目錄”,它是“從根路徑開始的、一個沒有通配符的最長路徑”。例如,"classpath: static/image/**/icon.png"的根目錄為"static/image",而"classpath: **/image/first/icon.png"的根目錄為""(后面我們稱為空目錄),另外"classpath: i*on.png"的根目錄也是""。
  可以看到,包含通配符的路徑,其提取出的根目錄有兩種情況,一種是空目錄,一種是包含了有效路徑的目錄,"classpath:"在這兩種不同的根目錄下查找行為有所區別,而"classpath*:"保持了一致,下面用一張表格歸納了它們的查找規則:
  根目錄類型 查找規則 進一步說明
classpath: 空目錄,即"" (1)只在類路徑中的擴展目錄下查找資源,無法在類路徑中的jar內查找。
(2)查找過程比較復雜:假設定義了3個類路徑,分別是擴展目錄"dir1"和"dir2",以及通配符jar包"dir3/*",它們的順序可以是任意的,如"dir1:dir2:dir3/*"或者"dir3/*:dir2:dir1"。step1.首先過濾掉類路徑中的jar包,剩下的擴展目錄保持定義順序不變;step2.按照類路徑定義的順序逐個查找擴展目錄,如果在某個擴展目錄下查找到匹配的資源文件,則將查找范圍鎖定在該擴展目錄下,並返回該擴展目錄下所有匹配的資源文件。
(1)返回的Resource都是FileSystemResource,從中獲取FileInputStream。
(2)實際上,之所以無法去jar包中查找,是因為ClassLoader的getResources方法在傳入""時,只能返回擴展目錄資源。
包含了有效路徑的目錄 (1)既可以查找類路徑中擴展目錄下的資源,也可以查找類路徑中jar包內的資源。
(2)查找過程比較復雜:假設定義了3個類路徑,分別是擴展目錄"dir1"和"dir2",以及通配符jar包"dir3/*",它們的順序可以是任意的,如"dir1:dir2:dir3/*"或者"dir3/*:dir2:dir1"。step1.直接按照類路徑定義的順序逐個查找擴展目錄或jar包,如果查找到包含“根目錄”的某個類路徑,則將查找范圍鎖定在此類路徑下,並返回此類路徑下所有匹配的資源文件,如果沒有匹配的就返回空。
(1)如果資源位於擴展目錄中,返回的Resource是FileSystemResource,從中獲取到FileInputStream。
(2)如果資源位於jar包中,返回的Resource是ClasspathResource,從中獲取到JarURLInputStream。
classpath*: 空目錄,即"" (1)既可以查找類路徑中擴展目錄下的資源,也可以查找類路徑中jar包內的資源。
(2)返回查找到的所有的匹配文件資源,因此可以不考慮查找順序。
(1)如果資源位於擴展目錄中,返回的Resource是FileSystemResource,從中獲取到FileInputStream。
(2)如果資源位於jar包中,返回的Resource是URLResource,從中獲取到JarURLInputStream。
包含了有效路徑的目錄
 

3.3.總結查找規則

  現在對上面分情況討論的查找規則進行一個總結,方便我們記憶和使用
  • "classpath*:":總是能在類路徑的擴展目錄和jar包中查找,並且返回所有的匹配資源。
  • "classpath:"(不含通配符):總是能按照類路徑定義的順序逐個查找擴展目錄或jar包,並返回第一個匹配的資源。
  • "classpath:"(含通配符):首先判斷根目錄是否為"",來決定查找的類路徑范圍是否需要過濾掉jar包。在處理后的類路徑中按照定義的順序逐一查找,直到查找出第一個匹配的資源文件,同時鎖定該資源文件所在的類路徑。之后查找並返回該鎖定的類路徑中所有匹配的資源文件。
 

四、框架和容器中的classpath


4.1.web容器的classpath

  傳統項目的部署方式是我們將應用打成war包,部署在tomcat或者jetty這樣的web容器中,然后啟動web容器,其classpath不需要我們手動設置,web容器在啟動時會幫我們設置好,並且約定俗稱的將“/path/to/WEB-INF/classes”與“/path/to/WEB-INF/lib”這兩個目錄設置成classpath,這也與war包的目錄結構保持一致:
  • WEB-INF/classes:存放src目錄java文件編譯之后的class文件、xml、properties等資源配置文件。
  • WEB-INF/lib:存放依賴庫。
 

4.2.springboot的classpath

  在springboot項目中,我們啟動可執行jar時一般不指定classpath,那么springboot的classpath如何指定的呢?我們先來看一個典型的springboot項目打包結構:

   在META-INF中存在清單文件MANIFEST.MF,打開該文件查看里面的內容,我們找到main-class屬性指定的並非是我們編寫的應用啟動類ServiceFeignApplication,而是org.springframework.boot.loader.JarLauncher。

  在可執行jar包對應的目錄中找到這個類,進行反編譯。通過反編譯代碼我們大概可以推斷出JarLauncher這個類添加了兩個新的classpath,即“path/to/BOOT-INF/classes”與“/path/to/BOOT-INF/lib”,這兩個目錄類似於傳統war包中的“WEB-INF/classes”與“WEB-INF/lib”。
  我們的研究當然不止於此,繼續深入的看一下springboot中的另外一個類“WebMVCAutoConfiguration”,其中的方法addResourcesHandlers調用了ResourcesProperties類的getLocations方法,在該方法中,我們發現了springboot定義了4個查找靜態資源或者配置文件的默認路徑,它們分別是:
  • classpath:/META-INF/resources/
  • classpath:/resources/
  • classpath:/static/
  • classpath:/public/
  如果將classpath代入,便可知靜態資源實際在jar中的查找位置了,這與springboot將靜態資源“resources/static”目錄的打包位置一致,下圖為jar包中“BOOT-INF/classes”目錄下的內容:

   

4.3.自定義打包的classpath

  之前做過一個項目,使用maven-assembly插件對springboot web項目進行定制打zip包,由於客戶規定了打包目錄結構,因此無法使用springboot默認的打包結構,包結構如下:
  為了使得springboot能夠正確查找到靜態頁面文件,選擇的解決方式是,在啟動腳本文件中,將“/path/to/WEB-INF”添加到classpath中,這樣就滿足了“classpath:/static/”的查找路徑,同時為了保證conf與lib目錄也能被查找,也將“/path/to/WEB-INF/conf”與“/path/to/WEB-INF/lib”添加到classpath中。    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM