Bagaimana cara membaca semua kelas dari paket Java di classpath?

94

Saya perlu membaca kelas yang terdapat dalam paket Java. Kelas-kelas itu ada di classpath. Saya perlu melakukan tugas ini dari program Java secara langsung. Apakah Anda tahu cara sederhana untuk melakukannya?

List<Class> classes = readClassesFrom("my.package")
Jørgen R
sumber
7
Sederhananya, tidak, Anda tidak dapat melakukannya dengan mudah. Ada beberapa trik yang sangat panjang yang berhasil dalam beberapa situasi, tetapi saya sangat menyarankan desain yang berbeda.
Skaffman
Solusinya mungkin ditemukan di proyek Weld .
Ondra Žižka
Lihat tautan ini untuk jawaban: stackoverflow.com/questions/176527/…
Karthik E

Jawaban:

48

Jika Anda memiliki Spring di classpath Anda maka yang berikut ini akan melakukannya.

Temukan semua kelas dalam paket yang dianotasi dengan 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;
}
Shalom938
sumber
Terima kasih untuk cuplikan kode. :) Jika Anda ingin menghindari duplikasi Kelas dalam daftar kandidat, ubah jenis koleksi dari Daftar ke Set. Dan gunakan hasEnclosingClass () dan getEnclosingClassName () dari classMetaData. Gunakan metode getEnclosingClass tentang kelas yang mendasari
traeper
29

Anda dapat menggunakan Proyek Refleksi yang dijelaskan di sini

Cukup lengkap dan mudah digunakan.

Deskripsi singkat dari situs web di atas:

Reflections memindai jalur kelas Anda, mengindeks metadata, memungkinkan Anda untuk menanyakannya pada waktu proses dan dapat menyimpan serta mengumpulkan informasi tersebut untuk banyak modul dalam proyek Anda.

Contoh:

Reflections reflections = new Reflections(
    new ConfigurationBuilder()
        .setUrls(ClasspathHelper.forJavaClassPath())
);
Set<Class<?>> types = reflections.getTypesAnnotatedWith(Scannable.class);
Renato
sumber
Apakah bekerja di Equinox? Dan jika ya, dapatkah itu dilakukan tanpa mengaktifkan plugin?
Tandai
bekerja untuk ekuinoks. pada Reflections versi lama, tambahkan bundle jar vfsType. lihat di sini
zapp
28

Saya menggunakan yang ini, ini berfungsi dengan file atau arsip 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;
}
Edoardo Panfili
sumber
1
ini berfungsi jika Anda mengambil referensi ke File.Separator dan hanya menggunakan '/'
Will Glass
1
Satu perubahan lagi diperlukan agar ini dapat bekerja dengan file jar jika jalur berisi spasi. Anda harus memecahkan kode jalur file jar. Ubah: jarFileName = packageURL.getFile (); ke: jarFileName = URLDecoder.decode (packageURL.getFile ());
Will Glass
saya hanya mendapatkan nama paket di jar..tidak mendapatkan nama kelas
AutoMEta
File baru (uri) akan memperbaiki masalah Anda dengan spasi.
Trejkaz
entryName.lastIndexOf ('.') akan menjadi -1
marstone
11

Spring telah mengimplementasikan fungsi pencarian classpath yang sangat baik di file PathMatchingResourcePatternResolver. Jika Anda menggunakan classpath*awalan:, Anda dapat menemukan semua sumber daya, termasuk kelas dalam hierarki tertentu, dan bahkan memfilternya jika Anda mau. Kemudian Anda dapat menggunakan turunan dari AbstractTypeHierarchyTraversingFilter, AnnotationTypeFilterdan AssignableTypeFilteruntuk memfilter sumber daya tersebut baik pada anotasi tingkat kelas atau pada antarmuka yang mereka implementasikan.

John Ellinwood
sumber
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[]{});
}

Solusi ini telah diuji dalam lingkungan EJB .

Paul Kuit
sumber
6

Scannotation dan Reflections menggunakan pendekatan pemindaian jalur kelas:

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

Pendekatan lain adalah dengan menggunakan Java Pluggable Annotation Processing API untuk menulis pemroses anotasi yang akan mengumpulkan semua kelas beranotasi pada waktu kompilasi dan membangun file indeks untuk penggunaan runtime. Mekanisme ini diimplementasikan di perpustakaan ClassIndex :

Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");
Sławek
sumber
3

Fungsionalitas itu secara mencurigakan masih hilang dari API refleksi Java sejauh yang saya tahu. Anda bisa mendapatkan objek paket hanya dengan melakukan ini:

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

Tetapi seperti yang mungkin Anda perhatikan, itu tidak akan membiarkan Anda membuat daftar kelas dalam paket itu. Untuk saat ini, Anda harus mengambil pendekatan yang lebih berorientasi pada sistem file.

Saya menemukan beberapa contoh implementasi di posting ini

Saya tidak 100% yakin metode ini akan berfungsi ketika kelas Anda dimakamkan dalam file JAR, tetapi saya harap salah satunya melakukannya untuk Anda.

Saya setuju dengan @skaffman ... jika Anda memiliki cara lain untuk melakukan hal ini, saya sarankan untuk melakukannya.

Brent Menulis Kode
sumber
4
Tidak mencurigakan, hanya saja tidak seperti itu. Kelas tidak "termasuk" ke dalam paket, mereka memiliki referensi ke sana. Asosiasi tidak menunjuk ke arah lain.
Skaffman
1
@skaff Poin yang sangat menarik. Tidak pernah terpikir seperti itu. Jadi selama kita menelusuri jejak pemikiran itu, mengapa pengaitannya tidak dua arah (ini lebih untuk keingintahuan saya sendiri sekarang)?
Brent Menulis Kode
3

Mekanisme paling kuat untuk mencantumkan semua kelas dalam paket tertentu saat ini adalah ClassGraph , karena menangani larik mekanisme spesifikasi jalur kelas seluas mungkin , termasuk sistem modul JPMS yang baru. (Saya penulisnya.)

List<String> classNames;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
        .enableClassInfo().scan()) {
    classNames = scanResult.getAllClasses().getNames();
}
Luke Hutchison
sumber
Saya baru saja menemukan ini beberapa tahun kemudian. Ini adalah alat yang hebat! Ia bekerja jauh lebih cepat daripada refleksi dan saya suka itu tidak memanggil penginisialisasi statis. Hanya apa yang saya butuhkan untuk menyelesaikan masalah yang kami hadapi.
akagixxer
2

eXtcos terlihat menjanjikan. Bayangkan Anda ingin menemukan semua kelas yang:

  1. Memperluas dari kelas "Komponen", dan menyimpannya
  2. Dianotasi dengan "MyComponent", dan
  3. Berada dalam paket "umum".

Dengan eXtcos, ini sesederhana

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));
    }
});
noelob
sumber
2
  1. Bill Burke telah menulis ( artikel bagus tentang pemindaian kelas] dan kemudian dia menulis Scannotation .

  2. Hibernate telah menulis ini:

    • org.hibernate.ejb.packaging.Scanner
    • org.hibernate.ejb.packaging.NativeScanner
  3. CDI mungkin menyelesaikan ini, tetapi tidak tahu - belum menyelidiki sepenuhnya

.

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

Juga untuk anotasi:

abstract class MyAnnotationQualifier
extends AnnotationLiteral<Entity> implements Entity {}
Ondra Žižka
sumber
Tautan ke artikel sudah mati.
pengguna2418306
1

Saya kebetulan telah mengimplementasikannya, dan itu berhasil dalam banyak kasus. Karena panjang, saya simpan di file di sini .

Idenya adalah untuk menemukan lokasi file sumber kelas yang tersedia dalam banyak kasus (pengecualian yang diketahui adalah file kelas JVM - sejauh yang saya uji). Jika kode ada dalam direktori, pindai semua file dan hanya tempatkan file kelas. Jika kode ada dalam file JAR, pindai semua entri.

Metode ini hanya dapat digunakan jika:

  1. Anda memiliki kelas yang ada dalam paket yang sama dengan yang ingin Anda temukan, Kelas ini disebut SeedClass. Misalnya, jika Anda ingin membuat daftar semua kelas di 'java.io', kelas benih mungkin java.io.File.

  2. Kelas Anda berada dalam direktori atau dalam file JAR yang memiliki informasi file sumber (bukan file kode sumber, tetapi hanya file sumber). Sejauh yang saya coba, ini berfungsi hampir 100% kecuali kelas JVM (kelas-kelas itu datang dengan JVM).

  3. Program Anda harus memiliki izin untuk mengakses ProtectionDomain dari kelas tersebut. Jika program Anda dimuat secara lokal, seharusnya tidak ada masalah.

Saya telah menguji program hanya untuk penggunaan biasa, jadi mungkin masih ada masalah.

Saya harap ini membantu.

NawaMan
sumber
1
itu tampaknya menjadi hal yang sangat menarik! saya akan mencoba menggunakannya. Jika menurut saya bermanfaat, dapatkah saya menggunakan kode Anda dalam proyek sumber terbuka?
1
Saya sedang mempersiapkan untuk membuatnya menjadi open source juga. Jadi silakan: D
NawaMan
@NawaMan: Apakah Anda akhirnya membuka sumbernya? Jika ya: di mana kami dapat menemukan versi terakhir? Terima kasih!
Joanis
1

Berikut adalah opsi lain, sedikit modifikasi pada jawaban lain di atas / di bawah:

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

Kembali ketika applet adalah tempat umum, seseorang mungkin memiliki URL di classpath. Ketika classloader membutuhkan sebuah kelas, classloader akan mencari semua lokasi di classpath, termasuk resource http. Karena Anda dapat memiliki hal-hal seperti URL dan direktori di classpath, tidak ada cara mudah untuk mendapatkan daftar kelas yang pasti.

Namun, Anda bisa sangat dekat. Beberapa perpustakaan Spring melakukan ini sekarang. Anda bisa mendapatkan semua jar di classpath, dan membukanya seperti file. Anda kemudian dapat mengambil daftar file ini, dan membuat struktur data yang berisi kelas Anda.

brianegge.dll
sumber
0

gunakan pakar ketergantungan:

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

lalu gunakan kode ini:

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

Brent - alasan pengaitan adalah salah satu cara yang berkaitan dengan fakta bahwa setiap kelas pada komponen apa pun dari CLASSPATH Anda dapat mendeklarasikan dirinya sendiri dalam paket apa pun (kecuali untuk java / javax). Jadi tidak ada pemetaan SEMUA kelas dalam "paket" tertentu karena tidak ada yang tahu atau bisa tahu. Anda dapat memperbarui file jar besok dan menghapus atau menambahkan kelas. Ini seperti mencoba mendapatkan daftar semua orang yang bernama John / Jon / Johan di semua negara di dunia - tidak ada dari kita yang maha tahu sehingga tidak ada dari kita yang akan memiliki jawaban yang benar.

idarwin
sumber
Jawaban filosofis yang bagus, tetapi bagaimana cara kerja pemindaian kacang CDI? Atau bagaimana Hibernate memindai @ Entitas?
Ondra Žižka