Làm cách nào để tôi có được danh sách tất cả các triển khai của một giao diện theo lập trình trong Java? [đóng cửa]


81

Tôi có thể làm điều đó với sự phản chiếu hoặc một cái gì đó tương tự không?


Có thể làm điều đó với một số phím tắt kỳ diệu của Eclipse không? ?
Tomasz Waszczyk

4
Vui lòng cân nhắc chọn một câu trả lời.
Please_Dont_Bully_Me_SO_Lords

Câu trả lời:


56

Tôi đã tìm kiếm trong một thời gian và dường như có nhiều cách tiếp cận khác nhau, đây là bản tóm tắt:

  1. Thư viện phản xạ khá phổ biến nếu bạn không ngại thêm phần phụ thuộc. Nó sẽ trông như thế này:

    Reflections reflections = new Reflections("firstdeveloper.examples.reflections");
    Set<Class<? extends Pet>> classes = reflections.getSubTypesOf(Pet.class);
    
  2. ServiceLoader (theo câu trả lời erickson) và nó sẽ giống như sau:

    ServiceLoader<Pet> loader = ServiceLoader.load(Pet.class);
    for (Pet implClass : loader) {
        System.out.println(implClass.getClass().getSimpleName()); // prints Dog, Cat
    }
    

    Lưu ý rằng để điều này hoạt động, bạn cần phải xác định Petlà ServiceProviderInterface (SPI) và khai báo các triển khai của nó. bạn làm điều đó bằng cách tạo một tệp resources/META-INF/servicesvới tên examples.reflections.Petvà khai báo tất cả các triển khai Pettrong đó

    examples.reflections.Dog
    examples.reflections.Cat
    
  3. chú thích cấp gói . đây là một ví dụ:

    Package[] packages = Package.getPackages();
    for (Package p : packages) {
        MyPackageAnnotation annotation = p.getAnnotation(MyPackageAnnotation.class);
        if (annotation != null) {
            Class<?>[]  implementations = annotation.implementationsOfPet();
            for (Class<?> impl : implementations) {
                System.out.println(impl.getSimpleName());
            }
        }
    }
    

    và định nghĩa chú thích:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.PACKAGE)
    public @interface MyPackageAnnotation {
        Class<?>[] implementationsOfPet() default {};
    }
    

    và bạn phải khai báo chú thích mức gói trong một tệp có tên package-info.javabên trong gói đó. đây là nội dung mẫu:

    @MyPackageAnnotation(implementationsOfPet = {Dog.class, Cat.class})
    package examples.reflections;
    

    Lưu ý rằng chỉ những gói được biết đến với ClassLoader tại thời điểm đó mới được tải bởi một cuộc gọi đến Package.getPackages().

Ngoài ra, có những cách tiếp cận khác dựa trên URLClassLoader sẽ luôn bị giới hạn đối với các lớp đã được tải, trừ khi bạn thực hiện tìm kiếm dựa trên thư mục.


Cách tiếp cận nào trong ba cách tiếp cận hiệu quả hơn và nhanh nhất?
carlspring

@carlspring Tôi không thể đưa ra tuyên bố tuyệt đối về hiệu quả tương đối của chúng nhưng giải pháp thay thế đầu tiên (thư viện phản xạ) hoạt động khá tốt đối với tôi.
Ahmad Abdelghany

DriverManager của JDBC hoạt động như thế nào? Không phải nó đang làm điều tương tự (tìm kiếm tất cả các triển khai của giao diện Driver trong classpath)?
Alex Semeniuk

1
@AlexSemeniuk Tôi nghĩ giờ đây họ hỗ trợ cơ chế Trình tải / Nhà cung cấp Dịch vụ (cách tiếp cận # 2 ở trên) theo tài liệu của họ "Các phương thức DriverManager getConnection và getDrivers đã được cải tiến để hỗ trợ cơ chế Nhà cung cấp Dịch vụ Phiên bản Tiêu chuẩn Java." Xem docs.oracle.com/javase/8/docs/api/java/sql/DriverManager.html
Ahmad Abdelghany

1
Cần lưu ý rằng cơ chế nhà cung cấp dịch vụ đã được tích hợp vào hệ thống mô-đun với Java 9, điều này làm cho nó thậm chí còn thuận tiện hơn các chú thích cấp độ gói, tức là bạn không cần phải tạo loại chú thích của riêng mình, có thể khai báo các triển khai trong mô-đun -info, bạn vẫn sẽ tạo khi viết phần mềm mô-đun và nhận phản hồi thời gian biên dịch về tính hợp lệ của việc triển khai.
Holger

28

Điều mà erickson đã nói, nhưng nếu bạn vẫn muốn làm điều đó thì hãy xem Reflections . Từ trang của họ:

Sử dụng Phản ánh, bạn có thể truy vấn siêu dữ liệu của mình cho:

  • lấy tất cả các loại phụ của một số loại
  • nhận tất cả các loại được chú thích với một số chú thích
  • nhận tất cả các loại được chú thích với một số chú thích, bao gồm các thông số chú thích khớp
  • lấy tất cả các phương thức được chú thích bằng một số

12
cụ thể hơn:new Reflections("my.package").getSubTypesOf(MyInterface.class)
zapp

27

Nói chung, rất tốn kém để làm điều này. Để sử dụng phản xạ, lớp phải được tải. Nếu bạn muốn tải mọi lớp có sẵn trên classpath, điều đó sẽ tốn thời gian và bộ nhớ và không được khuyến khích.

Nếu muốn tránh điều này, bạn cần triển khai trình phân tích cú pháp tệp lớp của riêng mình hoạt động hiệu quả hơn, thay vì phản chiếu. Thư viện kỹ thuật mã byte có thể giúp thực hiện phương pháp này.

Các cơ chế cung cấp dịch vụ là phương tiện thông thường để liệt kê các hiện thực của một dịch vụ pluggable, và đã trở thành thành lập hơn với sự ra đời của dự án Jigsaw (module) trong Java 9. Sử dụng ServiceLoadertrong Java 6, hoặc thực hiện của riêng bạn trong các phiên bản trước đó. Tôi đã cung cấp một ví dụ trong một câu trả lời khác.


4
Thật không may, cơ chế Nhà cung cấp dịch vụ yêu cầu bạn liệt kê các lớp có thể nằm trong danh sách quan tâm trong một tệp riêng biệt, điều này thường không khả thi.
Averagesko

4
Việc viết mã nguồn triển khai một giao diện, biên dịch và triển khai nó là khả thi, nhưng không thể đưa vào một tệp văn bản với FQN của triển khai? Đó là trường hợp hiếm khi xảy ra. Hầu như không "thường xuyên".
erickson

DriverManager của JDBC hoạt động như thế nào? Không phải nó đang làm điều tương tự (tìm kiếm tất cả các triển khai của giao diện Driver trong classpath)?
Alex Semeniuk

1
@AlexSemeniuk Không. Nó sử dụng cơ chế Service Loader mà tôi mô tả ở trên; Trình điều khiển JDBC 4.0+ phải liệt kê tên của nó trongMETA-INF/services/java.sql.Driver
erickson

14

Spring có một cách khá đơn giản để đạt được điều này:

public interface ITask {
    void doStuff();
}

@Component
public class MyTask implements ITask {
   public void doStuff(){}
}

Sau đó, bạn có thể tự động tạo một danh sách các loại ITask và Spring sẽ điền nó với tất cả các triển khai:

@Service
public class TaskService {

    @Autowired
    private List<ITask> tasks;
}

4
Không chính xác, Spring sẽ điền vào nó tất cả các loại đậu thuộc loại ITask, không hoàn toàn giống nhau.
Olivier Gérardin

5

Cơ chế mạnh mẽ nhất để liệt kê tất cả các lớp triển khai một giao diện nhất định hiện là ClassGraph , bởi vì nó xử lý mảng cơ chế đặc tả classpath rộng nhất có thể , bao gồm cả hệ thống mô-đun JPMS mới. (Tôi là tác giả.)

try (ScanResult scanResult = new ClassGraph().whitelistPackages("x.y.z")
        .enableClassInfo().scan()) {
    for (ClassInfo ci : scanResult.getClassesImplementing("x.y.z.SomeInterface")) {
        foundImplementingClass(ci);  // Do something with the ClassInfo object
    }
}

1
Thư viện ClassGraph hoạt động như một sharm, cảm ơn bạn @Luke Hutchison
Kyrylo Semenko


4

Những gì erikson nói là tốt nhất. Đây là một chuỗi câu hỏi và câu trả lời liên quan - http://www.velocityreviews.com/forums/t137693-find-all-implecting-classes-in-classpath.html

Thư viện Apache BCEL cho phép bạn đọc các lớp mà không cần tải chúng. Tôi tin rằng nó sẽ nhanh hơn vì bạn có thể bỏ qua bước xác minh. Một vấn đề khác khi tải tất cả các lớp bằng trình tải lớp là bạn sẽ phải chịu một tác động lớn về bộ nhớ cũng như vô tình chạy bất kỳ khối mã tĩnh nào mà bạn có thể không muốn làm.

Liên kết thư viện Apache BCEL - http://jakarta.apache.org/bcel/


4

Với ClassGraph, nó khá đơn giản:

Mã Groovy để tìm các triển khai của my.package.MyInterface:

@Grab('io.github.classgraph:classgraph:4.6.18')
import io.github.classgraph.*
new ClassGraph().enableClassInfo().scan().withCloseable { scanResult ->
    scanResult.getClassesImplementing('my.package.MyInterface').findAll{!it.abstract}*.name
}

Bạn cần gọi scan().withCloseable { ... }trong Groovy, hoặc sử dụng try-with-resources trong Java: github.com/classgraph/classgraph/wiki/… Ngoài ra, phần cuối cùng nên là .name, không .className, vì đây .getName()là phương pháp phù hợp để lấy tên của một lớp từ một ClassInfođối tượng.
Luke Hutchison

2

Một phiên bản mới của câu trả lời của @ kaybee99, nhưng giờ đây trả lại những gì người dùng yêu cầu: triển khai ...

Spring có một cách khá đơn giản để đạt được điều này:

public interface ITask {
    void doStuff();
    default ITask getImplementation() {
       return this;
    }

}

@Component
public class MyTask implements ITask {
   public void doStuff(){}
}

Sau đó, bạn có thể tự động tạo một danh sách các loạiITask và Spring sẽ điền nó với tất cả các triển khai:

@Service
public class TaskService {

    @Autowired(required = false)
    private List<ITask> tasks;

    if ( tasks != null)
    for (ITask<?> taskImpl: tasks) {
        taskImpl.doStuff();
    }   
}

1

Ngoài ra, nếu bạn đang viết một plugin IDE (nơi mà những gì bạn đang cố gắng thực hiện là tương đối phổ biến), thì IDE thường cung cấp cho bạn những cách hiệu quả hơn để truy cập phân cấp lớp của trạng thái hiện tại của mã người dùng.


1

Tôi gặp phải vấn đề tương tự. Giải pháp của tôi là sử dụng phản chiếu để kiểm tra tất cả các phương thức trong một lớp ObjectFactory, loại bỏ những phương thức không phải là phương thức createXXX () trả về một phiên bản của một trong những POJO bị ràng buộc của tôi. Mỗi lớp được phát hiện như vậy sẽ được thêm vào một mảng Lớp [], mảng này sau đó được chuyển tới lệnh gọi thuyết minh JAXBContext. Điều này hoạt động tốt, chỉ cần tải lớp ObjectFactory, lớp này sắp cần thiết. Tôi chỉ cần duy trì lớp ObjectFactory, một tác vụ được thực hiện bằng tay (trong trường hợp của tôi, vì tôi đã bắt đầu với POJO và sử dụng schemagen) hoặc có thể được tạo ra khi cần bằng xjc. Dù bằng cách nào, nó vẫn hiệu quả, đơn giản và hiệu quả.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.