Làm cách nào để đọc tất cả các lớp từ một gói Java trong classpath?


94

Tôi cần đọc các lớp có trong một gói Java. Các lớp đó nằm trong classpath. Tôi cần thực hiện tác vụ này trực tiếp từ một chương trình Java. Bạn có biết một cách đơn giản để làm không?

List<Class> classes = readClassesFrom("my.package")

7
Nói một cách đơn giản, không, bạn không thể làm điều đó một cách dễ dàng. Có một số thủ thuật cực kỳ dài dòng có hiệu quả trong một số tình huống, nhưng tôi thực sự đề xuất một thiết kế khác.
skaffman 21/09/09


Giải pháp có thể được tìm thấy trong dự án Weld .
Ondra Žižka,

Tham khảo liên kết này để biết câu trả lời: stackoverflow.com/questions/176527/…
Karthik E

Câu trả lời:


48

Nếu bạn có Spring in you classpath thì thao tác sau sẽ thực hiện.

Tìm tất cả các lớp trong một gói được chú thích bằng XmlRootElement:

private List<Class> findMyTypes(String basePackage) throws IOException, ClassNotFoundException
{
    ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

    List<Class> candidates = new ArrayList<Class>();
    String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                               resolveBasePackage(basePackage) + "/" + "**/*.class";
    Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
    for (Resource resource : resources) {
        if (resource.isReadable()) {
            MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
            if (isCandidate(metadataReader)) {
                candidates.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
            }
        }
    }
    return candidates;
}

private String resolveBasePackage(String basePackage) {
    return ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage));
}

private boolean isCandidate(MetadataReader metadataReader) throws ClassNotFoundException
{
    try {
        Class c = Class.forName(metadataReader.getClassMetadata().getClassName());
        if (c.getAnnotation(XmlRootElement.class) != null) {
            return true;
        }
    }
    catch(Throwable e){
    }
    return false;
}

Cảm ơn vì đoạn mã. :) Nếu bạn muốn tránh trùng lặp Lớp trong danh sách ứng viên, hãy thay đổi loại tập hợp từ Danh sách thành Tập hợp. Và sử dụng hasEnclosingClass () và getEnclosingClassName () của classMetaData. Phương pháp sử dụng getEnclosingClass về một lớp học cơ bản
traeper

29

Bạn có thể sử dụng Dự án Phản chiếu được mô tả ở đây

Nó khá đầy đủ và dễ sử dụng.

Mô tả ngắn gọn từ trang web trên:

Phản xạ quét đường dẫn classpath của bạn, lập chỉ mục siêu dữ liệu, cho phép bạn truy vấn nó trong thời gian chạy và có thể lưu và thu thập thông tin đó cho nhiều mô-đun trong dự án của bạn.

Thí dụ:

Reflections reflections = new Reflections(
    new ConfigurationBuilder()
        .setUrls(ClasspathHelper.forJavaClassPath())
);
Set<Class<?>> types = reflections.getTypesAnnotatedWith(Scannable.class);

Có hoạt động ở Equinox không? Và nếu vậy, nó có thể làm như vậy mà không cần kích hoạt plugin không?
Gắn thẻ

hoạt động cho phân. trên các phiên bản Reflections cũ hơn, hãy thêm gói vfsType. xem ở đây
zapp

28

Tôi sử dụng cái này, nó hoạt động với các tệp hoặc kho lưu trữ jar

public static ArrayList<String>getClassNamesFromPackage(String packageName) throws IOException{
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    URL packageURL;
    ArrayList<String> names = new ArrayList<String>();;

    packageName = packageName.replace(".", "/");
    packageURL = classLoader.getResource(packageName);

    if(packageURL.getProtocol().equals("jar")){
        String jarFileName;
        JarFile jf ;
        Enumeration<JarEntry> jarEntries;
        String entryName;

        // build jar file name, then loop through zipped entries
        jarFileName = URLDecoder.decode(packageURL.getFile(), "UTF-8");
        jarFileName = jarFileName.substring(5,jarFileName.indexOf("!"));
        System.out.println(">"+jarFileName);
        jf = new JarFile(jarFileName);
        jarEntries = jf.entries();
        while(jarEntries.hasMoreElements()){
            entryName = jarEntries.nextElement().getName();
            if(entryName.startsWith(packageName) && entryName.length()>packageName.length()+5){
                entryName = entryName.substring(packageName.length(),entryName.lastIndexOf('.'));
                names.add(entryName);
            }
        }

    // loop through files in classpath
    }else{
    URI uri = new URI(packageURL.toString());
    File folder = new File(uri.getPath());
        // won't work with path which contains blank (%20)
        // File folder = new File(packageURL.getFile()); 
        File[] contenuti = folder.listFiles();
        String entryName;
        for(File actual: contenuti){
            entryName = actual.getName();
            entryName = entryName.substring(0, entryName.lastIndexOf('.'));
            names.add(entryName);
        }
    }
    return names;
}

1
tác phẩm này nếu bạn đưa ra các tài liệu tham khảo để File.Separator và chỉ sử dụng '/'
Will Glass

1
Cần thêm một thay đổi nữa để điều này hoạt động với các tệp jar khi đường dẫn chứa khoảng trắng. Bạn phải giải mã đường dẫn tệp jar. Thay đổi: jarFileName = packageURL.getFile (); thành: jarFileName = URLDecoder.decode (packageURL.getFile ());
Will Glass

tôi nhận được chỉ tên gói trong jar..not nhận tên lớp
AutoMEta

Tệp mới (uri) sẽ khắc phục sự cố của bạn với khoảng trắng.
Trejkaz

entryName.lastIndexOf ('.') sẽ là -1
marstone

11

Spring đã triển khai một chức năng tìm kiếm classpath tuyệt vời trong PathMatchingResourcePatternResolver. Nếu bạn sử dụng classpath*tiền tố:, bạn có thể tìm thấy tất cả các tài nguyên, bao gồm các lớp trong một hệ thống phân cấp nhất định và thậm chí lọc chúng nếu bạn muốn. Sau đó, bạn có thể sử dụng con cái AbstractTypeHierarchyTraversingFilter, AnnotationTypeFilterAssignableTypeFilterđể lọc những nguồn lực hoặc chú thích mức lớp hoặc trên giao diện họ thực hiện.


6

Java 1.6.0_24:

public static File[] getPackageContent(String packageName) throws IOException{
    ArrayList<File> list = new ArrayList<File>();
    Enumeration<URL> urls = Thread.currentThread().getContextClassLoader()
                            .getResources(packageName);
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        File dir = new File(url.getFile());
        for (File f : dir.listFiles()) {
            list.add(f);
        }
    }
    return list.toArray(new File[]{});
}

Giải pháp này đã được thử nghiệm trong môi trường EJB .


6

ScannotationReflections sử dụng phương pháp quét đường dẫn lớp:

Reflections reflections = new Reflections("my.package");
Set<Class<? extends Object>> classes = reflections.getSubTypesOf(Object.class);

Một cách tiếp cận khác là sử dụng API xử lý chú thích có thể cắm được của Java để viết bộ xử lý chú thích sẽ thu thập tất cả các lớp chú thích tại thời điểm biên dịch và xây dựng tệp chỉ mục để sử dụng trong thời gian chạy. Cơ chế này được thực hiện trong thư viện ClassIndex :

Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");

3

Chức năng đó vẫn bị thiếu một cách đáng ngờ trong API phản chiếu Java theo như tôi biết. Bạn có thể lấy một đối tượng gói chỉ bằng cách thực hiện điều này:

Package packageObj = Package.getPackage("my.package");

Nhưng như bạn có thể nhận thấy, điều đó sẽ không cho phép bạn liệt kê các lớp trong gói đó. Ngay bây giờ, bạn phải thực hiện một cách tiếp cận theo định hướng hệ thống tệp hơn.

Tôi đã tìm thấy một số triển khai mẫu trong bài đăng này

Tôi không chắc 100% các phương pháp này sẽ hoạt động khi các lớp của bạn bị chôn vùi trong các tệp JAR, nhưng tôi hy vọng một trong số đó sẽ làm được điều đó cho bạn.

Tôi đồng ý với @skaffman ... nếu bạn có cách khác để giải quyết vấn đề này, tôi khuyên bạn nên làm điều đó thay thế.


4
Nó không đáng ngờ, nó chỉ không hoạt động theo cách đó. Các lớp không "thuộc về" các gói, chúng có các tham chiếu đến chúng. Hiệp hội không chỉ theo hướng khác.
skaffman 21/09/09

1
@skaffman Điểm rất thú vị. Không bao giờ nghĩ về nó theo cách đó. Vì vậy, chừng nào chúng ta còn lang thang theo dòng suy nghĩ đó, thì tại sao sự liên kết không có tính chất hai chiều (điều này là do tôi tò mò hơn bây giờ)?
Brent viết mã

3

Cơ chế mạnh mẽ nhất để liệt kê tất cả các lớp trong một gói nhất định hiện là ClassGraph , 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ả.)

List<String> classNames;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
        .enableClassInfo().scan()) {
    classNames = scanResult.getAllClasses().getNames();
}

Tôi chỉ biết điều này một vài năm sau đó. Đây là một công cụ tuyệt vời! Nó hoạt động nhanh hơn nhiều so với phản xạ và tôi thích rằng nó không gọi các bộ khởi tạo tĩnh. Chỉ những gì tôi cần để giải quyết một vấn đề mà chúng tôi gặp phải.
akagixxer

2

eXtcos có vẻ đầy hứa hẹn. Hãy tưởng tượng bạn muốn tìm tất cả các lớp:

  1. Mở rộng từ lớp "Thành phần" và lưu trữ chúng
  2. Được chú thích bằng "MyComponent" và
  3. Nằm trong gói "chung".

Với eXtcos, điều này đơn giản như

ClasspathScanner scanner = new ClasspathScanner();
final Set<Class> classStore = new ArraySet<Class>();

Set<Class> classes = scanner.getClasses(new ClassQuery() {
    protected void query() {
        select().
        from(“common”).
        andStore(thoseExtending(Component.class).into(classStore)).
        returning(allAnnotatedWith(MyComponent.class));
    }
});

2
  1. Bill Burke đã viết một ( bài báo hay về quét lớp) và sau đó ông viết Scannotation .

  2. Hibernate đã viết điều này:

    • org.hibernate.ejb.packaging.Scanner
    • org.hibernate.ejb.packaging.NativeScanner
  3. CDI có thể giải quyết vấn đề này, nhưng không biết - chưa điều tra đầy đủ

.

@Inject Instance< MyClass> x;
...
x.iterator() 

Cũng cho chú thích:

abstract class MyAnnotationQualifier
extends AnnotationLiteral<Entity> implements Entity {}

Liên kết đến bài báo đã chết.
dùng2418306

1

Tôi tình cờ đã triển khai nó và nó hoạt động trong hầu hết các trường hợp. Vì nó dài, tôi đã đưa nó vào một tập tin ở đây .

Ý tưởng là tìm vị trí của tệp nguồn lớp có sẵn trong hầu hết các trường hợp (một ngoại lệ đã biết là các tệp lớp JVM - theo như tôi đã thử nghiệm). Nếu mã nằm trong một thư mục, hãy quét qua tất cả các tệp và chỉ quét các tệp lớp. Nếu mã nằm trong tệp JAR, hãy quét tất cả các mục nhập.

Phương pháp này chỉ có thể được sử dụng khi:

  1. Bạn có một lớp nằm trong cùng một gói mà bạn muốn khám phá, Lớp này được gọi là SeedClass. Ví dụ: nếu bạn muốn liệt kê tất cả các lớp trong 'java.io', thì lớp hạt giống có thể là java.io.File.

  2. Các lớp của bạn nằm trong một thư mục hoặc trong một tệp JAR, nó có thông tin tệp nguồn (không phải tệp mã nguồn mà chỉ là tệp nguồn). Theo như tôi đã thử, nó hoạt động gần như 100% ngoại trừ lớp JVM (những lớp đó đi kèm với JVM).

  3. Chương trình của bạn phải có quyền truy cập vào ProtectionDomain của các lớp đó. Nếu chương trình của bạn được tải cục bộ, sẽ không có vấn đề gì.

Tôi đã thử nghiệm chương trình chỉ để sử dụng thường xuyên, vì vậy nó có thể vẫn gặp sự cố.

Tôi hi vọng cái này giúp được.


1
nó có vẻ là một thứ rất thú vị! tôi sẽ cố gắng sử dụng nó. Nếu tôi thấy hữu ích cho tôi, tôi có thể sử dụng mã của bạn trong một dự án nguồn mở không?

1
Tôi cũng đang chuẩn bị mã nguồn mở. Vì vậy, hãy tiếp tục: D
NawaMan

@NawaMan: Cuối cùng thì bạn đã mở nguồn nó? Nếu vậy: chúng ta có thể tìm thấy phiên bản cuối cùng ở đâu? Cảm ơn!
Joanis

1

Đây là một tùy chọn khác, sửa đổi một chút cho một câu trả lời khác ở trên / dưới:

Reflections reflections = new Reflections("com.example.project.package", 
    new SubTypesScanner(false));
Set<Class<? extends Object>> allClasses = 
    reflections.getSubTypesOf(Object.class);

0

Quay lại khi các applet là nơi phổ biến, một trong những có thể có một URL trên classpath. Khi trình nạp lớp yêu cầu một lớp, nó sẽ tìm kiếm tất cả các vị trí trên classpath, bao gồm cả tài nguyên http. Bởi vì bạn có thể có những thứ như URL và thư mục trên classpath, không có cách nào dễ dàng để có được danh sách chính xác của các lớp.

Tuy nhiên, bạn có thể đến khá gần. Một số thư viện Spring hiện đang làm việc này. Bạn có thể lấy tất cả các jar trên classpath và mở chúng lên như các tệp. Sau đó, bạn có thể lấy danh sách tệp này và tạo cấu trúc dữ liệu chứa các lớp của bạn.


0

sử dụng maven phụ thuộc:

groupId: net.sf.extcos
artifactId: extcos
version: 0.4b

sau đó sử dụng mã này:

ComponentScanner scanner = new ComponentScanner();
        Set classes = scanner.getClasses(new ComponentQuery() {
            @Override
            protected void query() {
                select().from("com.leyton").returning(allExtending(DynamicForm.class));
            }
        });

-2

Brent - lý do liên kết là một cách liên quan đến thực tế là bất kỳ lớp nào trên bất kỳ thành phần nào của CLASSPATH của bạn đều có thể tự khai báo trong bất kỳ gói nào (ngoại trừ java / javax). Vì vậy, không có ánh xạ của TẤT CẢ các lớp trong một "gói" đã cho bởi vì không ai biết và cũng không thể biết. Bạn có thể cập nhật tệp jar vào ngày mai và xóa hoặc thêm lớp. Nó giống như việc cố gắng lấy một danh sách tất cả những người có tên John / Jon / Johan ở tất cả các quốc gia trên thế giới - không ai trong chúng ta là người toàn trí, do đó không ai trong chúng ta sẽ có câu trả lời chính xác.


Câu trả lời triết học tuyệt vời, nhưng sau đó quét hạt CDI hoạt động như thế nào? Hoặc làm thế nào để Hibernate quét @Entities?
Ondra Žižka,
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.