CodeGym /Các khóa học /JAVA 25 SELF /Đóng gói thông qua mô-đun: export và import

Đóng gói thông qua mô-đun: export và import

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Export gói: ai có thể thấy mã của bạn?

Trước Java 9, mọi thứ khá đơn giản — nhưng không thật sự an toàn. Nếu một lớp là public, bất kỳ ai có quyền truy cập vào JAR hoặc classpath đều có thể thấy nó. Điều này dẫn đến việc các lớp nội bộ và thậm chí cả những gói nguyên vẹn có thể bị dùng vô tình hoặc cố ý bên ngoài dự án của bạn. Đúng là bạn có thể ẩn bớt bằng private hoặc package-private, nhưng nếu lớp public — nó có sẵn cho tất cả mọi người.

Với sự xuất hiện của mô-đun, điều này đã thay đổi. Giờ đây, ngay cả khi một lớp được khai báo là public, bạn không thể sử dụng nó từ mô-đun khác nếu gói chứa nó chưa được export rõ ràng thông qua module-info.java.

Trông như thế nào trong thực tế

Giả sử bạn có cấu trúc dự án:

my-app/
  src/
    core/
      com/example/core/
        CoreService.java
        InternalHelper.java
      module-info.java
    app/
      com/example/app/
        Main.java
      module-info.java

Trong tệp core/module-info.java bạn viết:

module core {
    exports com.example.core;
}

Khi đó chỉ các lớp từ gói com.example.core mới khả dụng cho các mô-đun khác. Mọi thứ nằm trong các gói khác (ví dụ, com.example.core.internal) sẽ không thấy được — ngay cả khi ở đó có các lớp public. Đây chính là đóng gói theo mô-đun.

Mini demo: “thấy/không thấy”

Bên trong core:

// com/example/core/CoreService.java
package com.example.core;

public class CoreService {
    public void doWork() {
        System.out.println("Work done!");
    }
}

Bên trong app:

// com/example/app/Main.java
package com.example.app;

import com.example.core.CoreService;

public class Main {
    public static void main(String[] args) {
        CoreService service = new CoreService();
        service.doWork();
    }
}

Nếu bạn bỏ dòng exports com.example.core; khỏi core/module-info.java, khi biên dịch sẽ nhận lỗi:

error: package com.example.core is not visible

Ngay cả khi CoreServicepublic!

2. Nhập phụ thuộc: requires và cách ly nghiêm ngặt

Trong JPMS, bạn không thể tùy tiện sử dụng lớp từ mô-đun khác. Bạn phải khai báo phụ thuộc một cách rõ ràng bằng requires.

Ví dụ

Trong tệp app/module-info.java:

module app {
    requires core;
}

Bây giờ mô-đun app có thể sử dụng mọi thứ mà mô-đun core export.

Nếu bạn quên viết requires core;, sẽ nhận lỗi biên dịch:

error: package com.example.core is not visible

hoặc

error: cannot access CoreService

Điểm quan trọng

  • requires hoạt động ở cấp mô-đun, không phải cấp gói.
  • Bạn không thể “import” chỉ một gói — chỉ toàn bộ mô-đun được export.

3. So sánh: mô-đun với public/private

Mô-đun bổ sung một cấp độ đóng gói mới, nằm “bên trên” lớp và gói.

Cấp độ Quy định phạm vi gì? Hoạt động thế nào?
private Truy cập trong nội bộ lớp Chỉ trong một tệp
package-private (mặc định) Truy cập trong nội bộ gói Tất cả lớp trong cùng một gói
public Truy cập cho mọi nơi Bất kỳ mã nào ở bất kỳ đâu
module Truy cập giữa các mô-đun Chỉ các gói được export

Ý tưởng then chốt:

  • Một lớp có thể là public, nhưng nếu gói của nó không được export, nó chỉ khả dụng trong nội bộ mô-đun.
  • Export gói thông qua exports — như một “cửa sổ” qua đó mã của bạn được các mô-đun khác nhìn thấy.

Ví dụ: ẩn phần hiện thực

// com/example/core/internal/SecretSauce.java
package com.example.core.internal;

public class SecretSauce {
    public void addMagic() {}
}

Nếu bạn KHÔNG export gói com.example.core.internal trong module-info.java, thì không mô-đun bên ngoài nào có thể dùng lớp này, ngay cả khi nó là public!

4. Ví dụ: core và app — API và hiện thực

Xem một kịch bản điển hình: mô-đun core cung cấp API, mô-đun app — sử dụng nó.

Cấu trúc:

core/
  com/example/core/
    CoreAPI.java
  com/example/core/impl/
    CoreImpl.java
  module-info.java

app/
  com/example/app/
    Main.java
  module-info.java

core/module-info.java:

module core {
    exports com.example.core; // Chỉ API!
    // Không export com.example.core.impl
}

app/module-info.java:

module app {
    requires core;
}

com/example/core/CoreAPI.java:

package com.example.core;

public interface CoreAPI {
    void doSomething();
}

com/example/core/impl/CoreImpl.java:

package com.example.core.impl;

import com.example.core.CoreAPI;

public class CoreImpl implements CoreAPI {
    @Override
    public void doSomething() {
        System.out.println("Doing something!");
    }
}

com/example/app/Main.java:

package com.example.app;

import com.example.core.CoreAPI;
// import com.example.core.impl.CoreImpl; // Điều này sẽ gây lỗi biên dịch!

public class Main {
    public static void main(String[] args) {
        // CoreImpl impl = new CoreImpl(); // Lỗi! Gói không được export.
        // Chỉ có thể sử dụng những gì hiển thị qua API.
    }
}

Kết quả:

  • Mô-đun app chỉ thấy những gì core export.
  • Phần hiện thực (impl) được ẩn, ngay cả khi các lớp ở đó là public.

5. Thực hành: thử nghiệm với export và khả năng nhìn thấy

Bước 1: xóa export

Trong core/module-info.java, hãy comment dòng:

// exports com.example.core;

Bây giờ hãy thử biên dịch dự án. Hãy kỳ vọng lỗi biên dịch trong mô-đun app — nó sẽ không thấy các lớp từ com.example.core.

Bước 2: chỉ export API

Khôi phục dòng exports com.example.core;, nhưng đừng export com.example.core.impl. Hãy thử trong mô-đun app import một lớp từ impl. Bạn sẽ lại nhận lỗi biên dịch — mọi thứ hoàn toàn minh bạch!

Bước 3: lớp public trong gói không được export

Tạo một lớp public trong gói không được export. Hãy thử dùng nó từ mô-đun khác — sẽ không được. Đây là đóng gói theo mô-đun hoạt động trong thực tế.

6. Trông như thế nào trong IDE và “giải thích đơn giản”

  • Khi cố gắng import lớp từ gói không được export, IDE sẽ tô sáng lỗi.
  • Gợi ý sẽ giải thích rằng gói không được mô-đun export.
  • Thêm exports cho gói cần thiết — lỗi sẽ biến mất.

Sơ đồ: các mức độ truy cập

+---------------------+
|      MODULE         |
|  (module-info.java) |
+---------------------+
       |
       v
+---------------------+
|      PACKAGES       |
| (package, export)   |
+---------------------+
       |
       v
+---------------------+
|       CLASSES       |
| (public/private)    |
+---------------------+
       |
       v
+---------------------+
|   METHODS/FIELDS    |
| (public/private)    |
+---------------------+

7. Những điểm quan trọng và khác biệt

Export “chỉ cho bạn bè”: exports ... to

Đôi khi bạn cần export gói chỉ cho một số mô-đun nhất định (ví dụ, cho test hoặc các phần mở rộng đặc biệt):

exports com.example.core.internal to my.special.module, my.test.module;

Bây giờ chỉ các mô-đun được chỉ định mới thấy gói này.

Có thể export nhiều gói không?

Có! Chỉ cần thêm các dòng exports mới:

exports com.example.core;
exports com.example.core.api;

Có thể export “tất cả” không?

Không. Trong hệ thống mô-đun Java, bạn luôn chỉ định rõ ràng những gì cần export. Đó chính là sức mạnh của nó.

8. Các lỗi thường gặp khi làm việc với đóng gói theo mô-đun

Lỗi #1: Mong đợi rằng lớp public luôn nhìn thấy được.
Nếu gói không được export thông qua module-info.java, lớp sẽ không thấy được từ các mô-đun khác, ngay cả khi nó là public. Đây là “cái bẫy” phổ biến nhất cho người mới.

Lỗi #2: Quên khai báo requires.
Nếu mô-đun của bạn sử dụng các lớp từ mô-đun khác nhưng không viết requires, bạn sẽ nhận lỗi biên dịch. Đừng quên mô tả phụ thuộc một cách rõ ràng.

Lỗi #3: Cố gắng export cùng một gói từ hai mô-đun.
Trong JPMS, mỗi gói chỉ có thể được export bởi một mô-đun. Nếu vi phạm quy tắc này, trình biên dịch sẽ dừng bạn lại.

Lỗi #4: Sai cấu trúc — module-info.java không ở đúng chỗ.
Tệp module-info.java phải nằm ở gốc mã nguồn của mô-đun, nếu không mô-đun sẽ không được nhận diện.

Lỗi #5: Phụ thuộc vòng giữa các mô-đun.
Nếu mô-đun A requires B, còn B requires A — bạn sẽ nhận lỗi. Hãy tránh các vòng lặp trong đồ thị phụ thuộc.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION