Classpath và biên dịch Java từ command line

Tìm hiểu về khái niệm Classpath trong Java. Hướng dẫn cách biên dịch code Java thành bytecode và chạy bytecode thông qua command line. 1. Sơ lược & chuẩn bị 1.1. javac và java command Bạn nào học Java cũng biết, để chạy được chương trình Java cần hai bước biên dịch thành bytecode và

Tìm hiểu về khái niệm Classpath trong Java. Hướng dẫn cách biên dịch code Java thành bytecode và chạy bytecode thông qua command line.

1. Sơ lược & chuẩn bị

1.1. javacjava command

Bạn nào học Java cũng biết, để chạy được chương trình Java cần hai bước biên dịch thành bytecode và thực thi bytecode đó. Với mỗi giai đoạn sẽ dùng command tương ứng là javacjava như sau.

# Biên dịch Main.java thành Main.class
javac Main.java

# Chạy bytecode trong Main.class
java Main

Chương trình đơn giản như Hello World sẽ biên dịch bình thường mà không cần cấu hình classpath. Tuy nhiên, nếu chương trình có import qua lại với nhau, thì nên biết thêm về classpath.

1.2. Code ví dụ

Để test được classpath, mình sẽ tạo chương trình demo gồm 3 file như sau.

importtoyota.Car;publicclassMain{publicstaticvoidmain(String[] args){System.out.println("Bike: "+Bike.WHEEL_COUNT);System.out.println("Car: "+Car.WHEEL_COUNT);}}

Và file code thứ hai Bike.java nằm cùng thư mục với Main.java.

publicclassBike{publicstaticint WHEEL_COUNT =2;}

File code thứ ba nằm trong một thư mục con toyota (test package hoạt động ra sao khi custom classpath).

packagetoyota;publicclassCar{publicstaticint WHEEL_COUNT =4;}

Việc biên dịch và chạy chương trình không có lỗi gì cả. Quá trình biên dịch tạo ra 3 file class tương ứng nằm cùng vị trí với file code.

Biên dịch Main.java cho ra 3 file .class, nằm cùng vị trí với file code tương ứng. Các class liên quan, được sử dụng trong Main.java cũng được biên dịch theo, ngược lại những class không dùng đến thì không.

image.png

Dùng flag -d khi biên dịch sẽ cho ra các file class nằm trong một thư mục riêng (nhưng vẫn giữ cấu trúc như các file code).

javac -d ./target Main.java

Cũng có thể dùng dấu wildcard * để biên dịch toàn bộ file trong thư mục nào đó (các thư mục con thì không).

javac ./toyota/*.java

2. Khái niệm Classpath

2.1. Classpath khi compile

Chương trình ở phần trên chạy không có lỗi, do mặc định classpath được đặt là thư mục hiện tại. Vô tình classpath này lại khớp với các package và chương trình chạy được.

Tuy nhiên, hãy thay đổi một chút bằng cách di chuyển ra ngoài thư mục gốc (thư mục chứa file Main.java) và compile lại. Đã có lỗi xuất hiện như hình sau.

image.png

Java Compiler không thể tìm thấy các class liên quan (BikeCar) khi biên dịch Main class. Đây là do sự không phù hợp giữa khai báo package và classpath.

Khi biên dịch Java Compiler sẽ tìm kiếm các class khác liên quan. Class cần tìm sẽ bao gồm package (dựa theo import) và tên class, ví dụ toyota.Car.

Compiler dựa vào classpath, package và tên class tìm được file .java tương ứng. Ví dụ classpath đang ở . (mặc định), class là toyota.Car thì file Java sẽ nằm tại ./toyota/Car.java.

Compiler sẽ phân tích file Java và tìm class cần tìm, nếu có thì biên dịch thành file .class mới, ngược lại báo lỗi.
Lỗi này có thể do class khai báo sai package nên tên class không khớp. Ví dụ cần tìm toyota.Car mà trong file Java lại khai báo là yamaha.Car.

Quay lại trên, do classpath chưa đúng nên Java Compiler không thể tìm được các file .java liên quan để biên dịch. Vì vậy, cần chỉ định classpath khi biên dịch bằng flag -cp hoặc -classpath như sau.

javac -cp ./test-classpath ./test-classpath/Main.java

Ví dụ tìm class Bike, lúc trước classpath là ., nối với package của class Bike là rỗng (default package), nên file Bike.java sẽ nằm ở ./Bike.java. Nhưng đường dẫn không khớp nên bị lỗi.

Nhưng khi chỉnh lại classpath như trên thành ./test-classpath, nên file Bike.java nằm tại ./test-classpath/Bike.java. Java Compiler tìm được và biên dịch bình thường.

Tóm lại, classpath chỉ là một đường dẫn để javacjava tìm được đâu là gốc của package, dựa vào đó để tìm các file .java khác thôi.

2.2. Classpath ở runtime

Đúng ra thì khi biên dịch với javac phải dùng -sourcepath mới đúng. Cơ bản thì sourcepath với classpath chỉ khác nhau là một cái dùng khi compile, một cái dùng khi chạy bytecode. Mà với classpath thì dùng được ở cả 2 luôn, nên mình prefer hơn.

Như phần trên, command java được dùng để thực thi bytecode đã biên dịch. Lúc này cũng cần chú ý đến classpath nếu không muốn bị ClassNotFoundException.

image.png

Ở đây mình biên dịch vào thư mục ./target, nên classpath sẽ là thư mục này.

# Biên dịch trước
javac -d ./target -cp ./test-classpath ./test-classpath/Main.java

# Chạy class Main
java -cp ./target Main

Phần này khác một tí, chúng ta sẽ chỉ định tên class cần chạy gồm package và class name như sau. Tất nhiên class phải có public static void main thì mới chạy được.

image.png

3. Các khía cạnh khác

3.1. Chỉ định nhiều classpath

Sẽ có trường hợp bạn cần chỉ định nhiều classpath cùng lúc, ví dụ như các file Java có cùng package nhưng khác thư mục. Trong trường hợp đó, flag -cp cần chỉ định nhiều thư mục như sau:

  • Trên Windows dùng dấu ; để phân tách
  • Trên Linux dùng dấu : để phân tách

Tiến hành sửa lại code như sau để kiểm tra.

// Bỏ dòng khai báo package// Car sẽ cùng default package với Main (nhưng khác thư mục)// package toyota;publicclassCar{publicstaticint WHEEL_COUNT =4;}
// Do cùng default package nên không cần import// import toyota.Car;publicclassMain{publicstaticvoidmain(String[] args){System.out.println("Bike: "+Bike.WHEEL_COUNT);System.out.println("Car: "+Car.WHEEL_COUNT);}}

Biên dịch và chạy chương trình đã sửa. Chương trình vẫn hoạt động bình thường.

# Biên dịch
javac -cp "./test-classpath:./test-classpath/toyota" ./test-classpath/Main.java

# Chạy bytecode
java -cp "./test-classpath:./test-classpath/toyota" Main

Trường hợp này Java Compiler sẽ tìm class trong tất cả classpath. Và file Car.java dù có khai báo package không cũng cho kết quả tương tự, vì compiler tìm ở hai classpath khác nhau (nhưng khi biên dịch ra thư mục riêng thì cấu trúc file .class sẽ khác).

Ngoài ra, nhiều classpath cũng hỗ trợ trong việc chạy bytecode với các file JAR ở phần tiếp theo.

3.2. Classpath với file JAR

Hầu như các thư viện Java hiện tại được đóng gói dưới dạng file JAR (Java Archive). Nhưng nếu muốn biên dịch và chạy chương trình gồm file JAR qua command line thì làm thế nào?

Ở đây mình dùng thư viện Apache Common Lang để demo. Chỉ cần download về, giải nén ra và copy file JAR vào thư mục nào đó là được.

https://commons.apache.org/proper/commons-lang/download_lang.cgi

File JAR trong trường hợp này có thể xem như một thư mục chứa các file .class (thử dùng WinRAR mở ra thử). Như trong hình, việc sử dụng file JAR giống như chỉ định thêm một classpath.

image.png

Cần chú ý ở đây là khi biên dịch, Java Compiler nếu tìm thấy file .class rồi thì không cần biên dịch file .java nữa. Thêm nữa, khi chỉ định classpath là JAR thì có thể dùng wildcard để load tất cả file .jar trong thư mục nào đó.

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ