CLASSPATH - nočná mora?

2007/09/04

Úvod

Asi každý pisateľ kódu v Jave sa stretol s výnimkou ClassNotFoundException, ktorá indikuje chybový stav, keď Java Virtual Machine nebola schopná nájsť binárny kód pre danú triedu (teda súbor .class). Táto výnimka je veľmi „obľúbená" hlavne v prípade, keď nie je k dispozícii žiadne integrované vývojové prostredie (IDE) a sme obmedzení len na prácu s príkazovým riadkom.

Vyhľadávanie .class súborov môže spôsobiť veľký hlavybôľ, ale pri prečítaní správnej dokumentácie sa ukáže, že sa riadi pevne danými pravidlami. Problémom je ale niekoľko faktorov – predovšetkým adresárová štruktúra, štruktúra balíčkov, premenná CLASSPATH a parameter -cp pre java.exe, ktoré treba vhodne zladiť.

Príklady

Vo všetkých príkladoch budeme predpokladať, že máme pevný adresár pre „projekt", povedzme C:\Projects\javacp, v ktorom budeme uchovávať zdrojové kódy.

Jednoduchá trieda, žiadne balíčky

Predpokladajme, že máme jednoduchú triedu HelloWorld, ktorá sa nenachádza v žiadnom balíčku.

public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello World!");
  }
}

Trieda HelloWorld sa podľa konvencií musí nachádzať v súbore HelloWorld.java a keďže neprislúcha žiadnemu balíčku, bude uložený priamo v projektovom adresári – teda v C:\Projects\javacp\HelloWorld.java.

Súbor môžeme jednoducho skompilovať

javac HelloWorld.java

čím vznikne C:\Projects\javacp\HelloWorld.class. Tento súbor spustíme zavolaním java.exe, teda

java HelloWorld

Program by mal podľa očakávaní vypísať

Hello World!

Z nejakého dôvodu sa však môže stať, že zažijeme typickú výnimku:

Exception in thread "main" java.lang.NoClassDefFoundError: HelloWorld

Zákernosťou môže byť to, že na našom počítači program môže ísť, ale v analogickom adresári na počítači suseda získame výnimku. Základným riešením ťažkého kalibru pre tento príklad je použitie „tajného parametra"

java -cp . HelloWorld

Ten prikáže virtuálnemu stroju, aby triedy pre program vyhľadával len a výhradne v aktuálnom adresári (teda v C:\Projects\javacp.)

Jednoduchá trieda v balíčku

Presťahujme teraz triedu HelloWorld do balíčka sk.java. To vyžaduje dva kroky:

package sk.java;

public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello World!");
  }
}
c:\projects\javacp>md sk\java

Pre istotu vymažme starý skompilovaný súbor (aby sme predišli zmäteniu):

rm HelloWorld.class

a presuňme do balíčka zdrojový súbor:

C:\projects\javacp>move HelloWorld.java C:\projects\javacp\sk\java\HelloWorld.java

Presuňme sa teraz na spodok hierarchie

C:\projects\javacp>cd C:\projects\javacp\sk\java

a spusťme kompilátor

javac HelloWorld.java

Po skompilovaní skúsme spustiť program HelloWorld tak, ako v predošlom prípade

java -cp . HelloWorld

Žiaľ, teraz sa skoro určite dožijeme výnimky:

C:\projects\javacp\sk\java>java -cp . HelloWorld
Exception in thread "main" java.lang.NoClassDefFoundError: HelloWorld (wrong name: sk/java/HelloWorld)
  at java.lang.ClassLoader.defineClass1(Native Method)

V tomto prípade java oznamuje, že sa snažíme spustiť HelloWorld s nesprávnou špecifikáciou balíčka. Triedy, ktoré sa nachádzajú v balíčku sa musia volať úplným menom a navyše zo správneho adresára.

HelloWorld teda spustíme z adresára C:\Projects\javacp príkazom

C:\Projects\javacp>java -cp . sk.java.HelloWorld

Pri spustení sa začne vyhľadávať trieda počnúc aktuálnym adresárom. Keďže sa nachádza v balíčkoch, prejde sa adresárová štruktúra a trieda sa hľadá v súbore sk/java/HelloWorld.

Premenná prostredia CLASSPATH

Premenná prostredia CLASSPATH umožňuje nahradiť neustále zapisovanie parametra -cp. Ak nastavíme CLASSPATH na ., môžeme parameter -cp vynechať. CLASSPATH nastavíme dočasne pomocou

SET CLASSPATH=.

Zjednodušené spúšťanie je potom cez

java sk.java.HelloWorld

Dosť často sa však stáva, že premenná CLASSPATH je už definovaná a nachádzajú sa v nej už nejaké adresáre, prípadne JAR súbory. Vypísať CLASSPATH môžeme jednoducho

SET CLASSPATH

Dodať ďalšie položky do existujúceho CLASSPATH môžeme pomocou

SET CLASSPATH=%CLASSPATH%;.;

Problém CLASSPATH a -cp

Značná väčšina problémov spočíva v nepochopení spolupráce CLASSPATH a parametra -cp.

Pravidlá pre vyhľadávanie tried

  1. Ak nie je definovaný ani CLASSPATH, ani parameter -cp, trieda sa hľadá v aktuálnom adresári, prípadne v adresároch pod aktuálnym adresárom v prípade tried v balíčkoch
  2. Ak je definovaný CLASSPATH, predošlé pravidlo sa ignoruje. Triedy sa hľadajú v adresároch/JARoch špecifikovaných v tejto systémovej premennej.
  3. Ak je špecifikovaný parameter -cp, predošlé pravidlá sa ignorujú (premenná CLASSPATH sa teda ignoruje). Triedy sa hľadajú v adresároch/JARoch špecifikovaných v tomto parametri.

Špeciálne treba upozorniť na nasledovné chovanie:

Ďalšie príklady

JAR súbory

Na JAR súbory sa možno pozerať ako na celú adresárovú/balíčkovú štruktúru zbalenú v jednom súbore s daným menom. JAR súbor možno pridať do CLASSPATH/-cp podobne ako akýkoľvek iný adresár.

Ak napr. máme JAR súbor C:\projects\javacp\log4j.jar a potrebujeme z neho využívať triedu org.apache.log4j.Logger, pridáme ho do CLASSPATH cez

SET CLASSPATH=%CLASSPATH%;C:\projects\javacp\log4j.jar

a prípadne do -cp cez

java -cp C:\projects\javacp\log4j.jar nazovABalicekTriedy

Ak by náš HelloWorld využíval knižnicu log4j a chceli by sme ho spustiť, príkaz by vyzeral:

C:\projects\javacp>java -cp .;log4j.jar sk.java.HelloWorld

Všimnite si, že aktuálny adresár je uvedený explicitne, z neho sa bude odvíjať vyhľadávanie triedy HelloWorld. JAR súbor log4j.jar sa bude tiež vyhľadávať v aktuálnom adresári.

Spustiteľné JAR súbory

JAR súbory je možné pripraviť na “priame” spustenie. Takéto priamo spustiteľné súbory je možné naštartovať cez

java -jar názovSúboru.jar

V tom prípade sa ignorujú všetky body v pravidlách pre vyhľadávanie tried a všetky triedy sa vyhľadávajú len v danom JAR súbore.

Príklad zo života

Majme triedu sk.java.MatrixSolver, ktorá využíva JAR súbor commons-math-1.1.jar. Adresárová štruktúra vyzerá:

|--bin
|--|--sk
|--|--|--java
|--|--|--|--MatrixSolver.class
|--src
|--|--sk
|--|--|--java
|--|--|--|--MatrixSolver.java
|--commons-logging.jar (JAR pre výpočet matíc)
|--javacp.jar (triedy projektu zabalené do spustiteľného JARu)

Program môžeme spustiť napr.

java -cp bin;commons-math-1.1.jar sk.java.MatrixSolver

V javacp.jar sa nachádza automaticky spustiteľná verzia programu. Lenže tá je závislá na commons-math-1.1.jar. Bez závislostí by sme ju spustili klasicky cez -jar parameter:

java -jar javacp.jar

Ale ako bolo povedané vyššie, -jar parameter ignoruje všetky predošlé body vyhľadávania a teda aj commons-math-1.1.jar. Nepomôže ani explicitné určenie:

c:\Projects\javacp>java -cp commons-math-1.1.jar -jar javacp.jar
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/math/linear/RealMatrix

V tomto prípade je možné vyriešiť problém pridaním oboch JARov do CLASSPATH a spustením hlavnej triedy:

c:\Projects\javacp>java -cp commons-math-1.1.jar;javacp.jar sk.java.MatrixSolver

Sumár a morálne ponaučenie

Referencie

>> Home