Bisakah Anda menemukan semua kelas dalam sebuah paket menggunakan refleksi?

528

Apakah mungkin untuk menemukan semua kelas atau antarmuka dalam paket yang diberikan? (Dengan cepat melihat misalnya Package, sepertinya tidak.)

Jonik
sumber
2
FYI solusinya Amit tautan ke karya, meskipun memiliki bug jika jalur kelas memiliki karakter spasi di dalamnya (dan mungkin juga untuk karakter non-alfanumerik lainnya). jika Anda menggunakannya dalam segala jenis kode produksi, lihat komentar saya untuk jawabannya untuk solusi.
Kip
2
Perhatikan juga pos ini .
barfuin
1
Lihat jawaban terkait: stackoverflow.com/a/30149061/4102160
Cfx
1
Perhatikan juga pos ini .
sp00m
1
Lihat jawaban saya di bawah tentang ClassGraph, saat ini metode yang paling kuat untuk memindai jalur classpath dan modul.
Luke Hutchison

Jawaban:

373

Karena sifat dinamis loader kelas, ini tidak mungkin. Loader kelas tidak diharuskan memberi tahu VM kelas mana yang dapat diberikannya, sebaliknya mereka hanya menyerahkan permintaan untuk kelas, dan harus mengembalikan kelas atau melemparkan pengecualian.

Namun, jika Anda menulis loader kelas Anda sendiri, atau memeriksa classpath dan itu guci, itu mungkin untuk menemukan informasi ini. Ini akan melalui operasi filesystem, dan bukan refleksi. Bahkan mungkin ada perpustakaan yang dapat membantu Anda melakukan ini.

Jika ada kelas yang dihasilkan, atau dikirimkan dari jarak jauh, Anda tidak akan dapat menemukan kelas-kelas itu.

Metode yang normal adalah sebaliknya mendaftarkan suatu kelas yang perlu Anda akses di suatu file, atau mereferensikannya di kelas yang berbeda. Atau cukup gunakan konvensi ketika datang ke penamaan.

Tambahan: Perpustakaan Refleksi akan memungkinkan Anda untuk mencari kelas di classpath saat ini. Ini dapat digunakan untuk mendapatkan semua kelas dalam satu paket:

 Reflections reflections = new Reflections("my.project.prefix");

 Set<Class<? extends Object>> allClasses = 
     reflections.getSubTypesOf(Object.class);
Staale
sumber
12
Ketidakmampuan untuk menanyakan nama kelas telah mengganggu saya sejak lama. Tentu, ini sulit dan kinerjanya dapat sangat bervariasi, dan untuk kelas muatan tertentu daftar tidak terdefinisi atau tidak terbatas, tetapi ada beberapa cara yang bisa dilakukan.
Mr. Shiny dan New New 宇
16
Perhatikan bahwa solusi ini tidak akan berfungsi karena secara default getSubTypesOf tidak mengembalikan subtipe Objek. Lihat solusi Aleksander Blomskøld untuk cara mengkonfigurasi SubTypeScanner.
Alex Spurling
17
Refleksi membutuhkan Jambu. Jambu itu besar. Versi 14.0.1 adalah 2.1MB.
mike jones
3
Tidak bekerja untuk saya. Mac OSX - Ketergantungan refleksi versi 0.9.9-RC1 (maven) - JDK 1.7. Pertimbangkan kembali jawaban yang diterima. Jawaban @ AleksanderBlomskøld adalah satu untuk pergi. !!!!!
Konstantinos Margaritis
68
Jika ini mengembalikan daftar kosong, inisialisasi objek Refleksi seperti ini: Refleksi refleksi = Refleksi baru ("your.package.here", SubTypesScanner baru (false));
João Rocha da Silva
186

Anda mungkin harus melihat pada perpustakaan Refleksi open source . Dengan itu Anda dapat dengan mudah mencapai apa yang Anda inginkan.

Pertama, atur indeks refleksi (agak berantakan karena mencari semua kelas dinonaktifkan secara default):

List<ClassLoader> classLoadersList = new LinkedList<ClassLoader>();
classLoadersList.add(ClasspathHelper.contextClassLoader());
classLoadersList.add(ClasspathHelper.staticClassLoader());

Reflections reflections = new Reflections(new ConfigurationBuilder()
    .setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
    .setUrls(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])))
    .filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("org.your.package"))));

Kemudian Anda dapat meminta semua objek dalam paket yang diberikan:

Set<Class<?>> classes = reflections.getSubTypesOf(Object.class);
Aleksander Blomskøld
sumber
6
Ah, ini dia: code.google.com/p/reflections/issues/detail?id=122 . Objek dikecualikan secara default, tetapi Anda dapat menyatukan kembali itu. Terima kasih telah menunjukkan saya ke perpustakaan ini, sangat bagus!
mtrc
1
Saya mengalami masalah pada Mac saya dengan kode ini (terkait dengan pustaka asli), tetapi menggunakan .addUrls(ClasspathHelper.forJavaClassPath())alih-alih memecahkannya untuk saya. Lebih sedikit kode juga!
David Pärsson
3
jika ada yang bertanya-tanya cara paling sederhana untuk mendapatkan paket default adalah memiliki awalan menjadi String kosong -> "".
JBA
2
Perpustakaan "Reflections" memiliki lisensi yang rumit: github.com/ronmamo/reflections/blob/master/COPYING.txt . Kuncinya adalah bahwa lisensi hanya memungkinkan penggunaan lisensi itu sendiri. Jadi untuk benar-benar menggunakan perpustakaan (bukan lisensi) setiap orang harus menghubungi penulis dan menegosiasikan ketentuan penggunaan.
Serge Rogatch
3
Serge, saya pikir Anda salah paham WTFPL: wtfpl.net Saya pikir WTFPL berarti Anda bebas melakukan apa pun yang Anda inginkan, tidak hanya dengan lisensi tetapi juga dengan kode
Richo
122

Google Guava 14 menyertakan kelas baru ClassPathdengan tiga metode untuk memindai kelas tingkat atas:

  • getTopLevelClasses()
  • getTopLevelClasses(String packageName)
  • getTopLevelClassesRecursive(String packageName)

Lihat ClassPathjavadocs untuk info lebih lanjut.

Christoph Leiter
sumber
ini bekerja untuk saya di mana Refleksi tidak dapat melakukan (tidak ada leluhur langsung yang sama, tidak ada penjelasan umum)
Riccardo Cossu
1
Seperti yang saya sebutkan dalam komentar di bawah ini , ClassPathditandai @Beta, jadi mungkin bukan ide yang baik untuk beberapa ...
Christian
1
Mengatakan bahwa ini berfungsi di mana refleksi tidak dilakukan agak aneh, solusinya tidak diragukan lagi diimplementasikan menggunakan fungsionalitas refleksi (dan class loader).
Maarten Bodewes
5
Saya pikir maksudnya perpustakaan Refleksi yang disebutkan dalam jawaban lain.
Christoph Leiter
Bekerja di bawah Java 11, jika menggunakan jambu biji versi 28.1-jre.
gorjanz
112

Anda bisa menggunakan metode 1 ini yang menggunakan ClassLoader.

/**
 * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
 *
 * @param packageName The base package
 * @return The classes
 * @throws ClassNotFoundException
 * @throws IOException
 */
private static Class[] getClasses(String packageName)
        throws ClassNotFoundException, IOException {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    assert classLoader != null;
    String path = packageName.replace('.', '/');
    Enumeration<URL> resources = classLoader.getResources(path);
    List<File> dirs = new ArrayList<File>();
    while (resources.hasMoreElements()) {
        URL resource = resources.nextElement();
        dirs.add(new File(resource.getFile()));
    }
    ArrayList<Class> classes = new ArrayList<Class>();
    for (File directory : dirs) {
        classes.addAll(findClasses(directory, packageName));
    }
    return classes.toArray(new Class[classes.size()]);
}

/**
 * Recursive method used to find all classes in a given directory and subdirs.
 *
 * @param directory   The base directory
 * @param packageName The package name for classes found inside the base directory
 * @return The classes
 * @throws ClassNotFoundException
 */
private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
    List<Class> classes = new ArrayList<Class>();
    if (!directory.exists()) {
        return classes;
    }
    File[] files = directory.listFiles();
    for (File file : files) {
        if (file.isDirectory()) {
            assert !file.getName().contains(".");
            classes.addAll(findClasses(file, packageName + "." + file.getName()));
        } else if (file.getName().endsWith(".class")) {
            classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
        }
    }
    return classes;
}

__________
1 Metode ini diambil dari http://snippets.dzone.com/posts/show/4831 , yang diarsipkan oleh Internet Archive, seperti yang ditautkan sekarang. Cuplikan ini juga tersedia di https://dzone.com/articles/get-all-classes-within-package .

Ahmed Ashour
sumber
6
Saya punya masalah dengan ini jika jalur saya termasuk spasi. Kelas URL lolos dari ruang %20, tetapi new File()konstruktor menganggapnya sebagai persen literal yang menandakan dua nol. Saya memperbaikinya dengan mengubah dirs.add(...)baris ini: dirs.add(new File(resource.toURI())); Ini juga berarti saya harus menambahkan URISyntaxExceptionklausa getClasses
pelemparan
19
Anda baru saja menyalin dari dzone.com/articles/get-all-classes-within-package ! silakan rujuk sumber lain kali
RS
19
1 karena solusi ini TIDAK memerlukan perpustakaan eksternal ... TIDAK PERNAH, benar-benar TIDAK PERNAH memasangkan kode Anda secara acak dengan perpustakaan hanya untuk mencapai satu hal kecil seperti ini. apakah Anda tahu bahwa Anda menambahkan permukaan serangan potensial untuk penyerang? Nov 2015 masalah Apache Commons ditemukan yang mengarah ke Remote Command Execution hanya dengan memiliki Apache Commons di classpath aplikasi yang disebarkan di Jboss / Weblogic [ foxglovesecurity.com/2015/11/06/…
sc0p
@Qix dengan benar mencatat bahwa kode ini tidak mendukung toples. Untuk mendukung toples & direktori . Kode diubah seperti yang tercantum di bawah ini:
user1523177
1
Solusi yang bagus, tetapi tampaknya lebih baik jika 'Class.forName (String className)' akan diganti dengan 'Class.forName (String className, inisialisasi boolean, loader ClassLoader)' di mana 'inisialisasi = false;' agar tidak membuat instance kelas.
Andrei_N
99

Musim semi

Contoh ini untuk Spring 4, tetapi Anda dapat menemukan pemindai classpath di versi sebelumnya juga.

// create scanner and disable default filters (that is the 'false' argument)
final ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
// add include filters which matches all the classes (or use your own)
provider.addIncludeFilter(new RegexPatternTypeFilter(Pattern.compile(".*")));

// get matching classes defined in the package
final Set<BeanDefinition> classes = provider.findCandidateComponents("my.package.name");

// this is how you can load the class type from BeanDefinition instance
for (BeanDefinition bean: classes) {
    Class<?> clazz = Class.forName(bean.getBeanClassName());
    // ... do your magic with the class ...
}

Google Jambu

Catatan: Di versi 14, API masih ditandai sebagai @Beta , jadi berhati-hatilah dalam kode produksi.

final ClassLoader loader = Thread.currentThread().getContextClassLoader();

for (final ClassPath.ClassInfo info : ClassPath.from(loader).getTopLevelClasses()) {
  if (info.getName().startsWith("my.package.")) {
    final Class<?> clazz = info.load();
    // do something with your clazz
  }
}
voho
sumber
5
Jawaban yang sangat bagus. Ada terlalu banyak solusi di sini yang verbose, tidak teruji, tidak bekerja! Yang ini fantastis: ringkas dan teruji (berasal dari Jambu Biji). Baik sekali! Ini berguna, layak mendapatkan lebih banyak upvotes.
JeanValjean
Sayangnya, ClassPathkelas di Guava juga ditandai dengan @Beta: "API yang ditandai dengan penjelasan @Beta di tingkat kelas atau metode dapat berubah. Mereka dapat dimodifikasi dengan cara apa pun, atau bahkan dihapus, dalam rilis besar apa pun. Jika kode Anda adalah perpustakaan itu sendiri (yaitu digunakan pada CLASSPATH pengguna di luar kendali Anda sendiri), Anda tidak boleh menggunakan API beta, kecuali jika Anda mengemasnya kembali ... " code.google.com/p/guava-libraries/#Important_Warnings
Christian
@Christian Poin bagus, saya belum memperhatikan! Terima kasih. Saya akan menambahkan jawaban lain menggunakan pemindai classpath Spring, yang pasti bukan beta.
voho
Untuk menemukan kelas statis bersarang menggunakan solusi jambu biji, getAllClasses()metode dapat digunakan.
v.ladynev
1
Solusi Spring adalah satu-satunya yang berfungsi jika dijalankan dari toples yang dapat dieksekusi.
Luke
38

Halo. Saya selalu memiliki beberapa masalah dengan solusi di atas (dan di situs lain).
Saya, sebagai pengembang, sedang memprogram addon untuk API. API mencegah penggunaan perpustakaan eksternal atau alat pihak ketiga mana pun. Setup juga terdiri dari campuran kode dalam file jar atau zip dan file kelas yang terletak langsung di beberapa direktori. Jadi kode saya harus dapat bekerja diseluruh pengaturan. Setelah banyak penelitian saya menemukan metode yang akan bekerja di setidaknya 95% dari semua pengaturan yang mungkin.

Kode berikut pada dasarnya adalah metode kerja keras yang akan selalu berhasil.

Kode:

Kode ini memindai paket yang diberikan untuk semua kelas yang termasuk di dalamnya. Ini hanya akan berfungsi untuk semua kelas saat ini ClassLoader.

/**
 * Private helper method
 * 
 * @param directory
 *            The directory to start with
 * @param pckgname
 *            The package name to search for. Will be needed for getting the
 *            Class object.
 * @param classes
 *            if a file isn't loaded but still is in the directory
 * @throws ClassNotFoundException
 */
private static void checkDirectory(File directory, String pckgname,
        ArrayList<Class<?>> classes) throws ClassNotFoundException {
    File tmpDirectory;

    if (directory.exists() && directory.isDirectory()) {
        final String[] files = directory.list();

        for (final String file : files) {
            if (file.endsWith(".class")) {
                try {
                    classes.add(Class.forName(pckgname + '.'
                            + file.substring(0, file.length() - 6)));
                } catch (final NoClassDefFoundError e) {
                    // do nothing. this class hasn't been found by the
                    // loader, and we don't care.
                }
            } else if ((tmpDirectory = new File(directory, file))
                    .isDirectory()) {
                checkDirectory(tmpDirectory, pckgname + "." + file, classes);
            }
        }
    }
}

/**
 * Private helper method.
 * 
 * @param connection
 *            the connection to the jar
 * @param pckgname
 *            the package name to search for
 * @param classes
 *            the current ArrayList of all classes. This method will simply
 *            add new classes.
 * @throws ClassNotFoundException
 *             if a file isn't loaded but still is in the jar file
 * @throws IOException
 *             if it can't correctly read from the jar file.
 */
private static void checkJarFile(JarURLConnection connection,
        String pckgname, ArrayList<Class<?>> classes)
        throws ClassNotFoundException, IOException {
    final JarFile jarFile = connection.getJarFile();
    final Enumeration<JarEntry> entries = jarFile.entries();
    String name;

    for (JarEntry jarEntry = null; entries.hasMoreElements()
            && ((jarEntry = entries.nextElement()) != null);) {
        name = jarEntry.getName();

        if (name.contains(".class")) {
            name = name.substring(0, name.length() - 6).replace('/', '.');

            if (name.contains(pckgname)) {
                classes.add(Class.forName(name));
            }
        }
    }
}

/**
 * Attempts to list all the classes in the specified package as determined
 * by the context class loader
 * 
 * @param pckgname
 *            the package name to search
 * @return a list of classes that exist within that package
 * @throws ClassNotFoundException
 *             if something went wrong
 */
public static ArrayList<Class<?>> getClassesForPackage(String pckgname)
        throws ClassNotFoundException {
    final ArrayList<Class<?>> classes = new ArrayList<Class<?>>();

    try {
        final ClassLoader cld = Thread.currentThread()
                .getContextClassLoader();

        if (cld == null)
            throw new ClassNotFoundException("Can't get class loader.");

        final Enumeration<URL> resources = cld.getResources(pckgname
                .replace('.', '/'));
        URLConnection connection;

        for (URL url = null; resources.hasMoreElements()
                && ((url = resources.nextElement()) != null);) {
            try {
                connection = url.openConnection();

                if (connection instanceof JarURLConnection) {
                    checkJarFile((JarURLConnection) connection, pckgname,
                            classes);
                } else if (connection instanceof FileURLConnection) {
                    try {
                        checkDirectory(
                                new File(URLDecoder.decode(url.getPath(),
                                        "UTF-8")), pckgname, classes);
                    } catch (final UnsupportedEncodingException ex) {
                        throw new ClassNotFoundException(
                                pckgname
                                        + " does not appear to be a valid package (Unsupported encoding)",
                                ex);
                    }
                } else
                    throw new ClassNotFoundException(pckgname + " ("
                            + url.getPath()
                            + ") does not appear to be a valid package");
            } catch (final IOException ioex) {
                throw new ClassNotFoundException(
                        "IOException was thrown when trying to get all resources for "
                                + pckgname, ioex);
            }
        }
    } catch (final NullPointerException ex) {
        throw new ClassNotFoundException(
                pckgname
                        + " does not appear to be a valid package (Null pointer exception)",
                ex);
    } catch (final IOException ioex) {
        throw new ClassNotFoundException(
                "IOException was thrown when trying to get all resources for "
                        + pckgname, ioex);
    }

    return classes;
}

Tiga metode ini memberi Anda kemampuan untuk menemukan semua kelas dalam paket yang diberikan.
Anda menggunakannya seperti ini:

getClassesForPackage("package.your.classes.are.in");

Penjelasan:

Metode pertama mendapatkan arus ClassLoader. Itu kemudian mengambil semua sumber daya yang berisi paket kata dan iterates dari ini URL. Itu kemudian menciptakan URLConnectiondan menentukan apa jenis URl yang kita miliki. Ini bisa berupa direktori ( FileURLConnection) atau direktori di dalam jar atau file zip ( JarURLConnection). Bergantung pada jenis koneksi apa kita memiliki dua metode berbeda akan dipanggil.

Pertama mari kita lihat apa yang terjadi jika itu a FileURLConnection.
Pertama-tama memeriksa apakah File yang dilewati ada dan merupakan direktori. Jika itu masalahnya memeriksa apakah itu adalah file kelas. Jika demikian suatu Classobjek akan dibuat dan dimasukkan ke dalam ArrayList. Jika ini bukan file kelas tetapi merupakan direktori, kita cukup beralih ke dalamnya dan melakukan hal yang sama. Semua case / file lainnya akan diabaikan.

Jika URLConnection ini adalah JarURLConnectionmetode pembantu pribadi lainnya akan dipanggil. Metode ini mengulangi semua Entri dalam arsip zip / jar. Jika satu entri adalah file kelas dan ada di dalam paket Classobjek akan dibuat dan disimpan di ArrayList.

Setelah semua sumber daya telah diuraikan itu (metode utama) mengembalikan isi ArrayListsemua kelas dalam paket yang diberikan, yang saat ini ClassLoadertahu tentang.

Jika proses gagal pada suatu titik a ClassNotFoundExceptionakan dilemparkan containg informasi rinci tentang penyebab pastinya.

BrainStone
sumber
4
Contoh ini tampaknya memerlukan impor sun.net.www.protocol.file.FileURLConnection, yang menghasilkan peringatan pada waktu kompilasi ("peringatan: sun.net.www.protocol.file.FileURLConnection adalah Sun proprietary API dan dapat dihapus dalam rilis mendatang"). Apakah ada alternatif untuk menggunakan kelas itu, atau bisakah peringatan itu ditekan menggunakan anotasi?
Christian
Metode ini tidak berfungsi untuk kelas bootstrap, seperti yang ada di java.lang, java.util, ... Mereka dapat ditemukan dengan mendapatkan System.getProperty ("sun.boot.class.path"), membelah dengan: atau; (tergantung pada OS), dan kemudian menjalankan versi yang sedikit dimodifikasi dari checkDirectory di atas dan checkJarFile.
coderforlife
1
Anda dapat menyiasati peringatan / kesalahan dengan menggunakan connection.getClass (). GetCanonicalName (). Sama dengan ("sun.net.www.protocol.file.FileURLConnection"). Jika Anda benar-benar ingin, Anda dapat membuat koneksi URLC yang menurut Anda HARUS menggunakan sun.net.www.protocol.file.FileURLConnection dan juga membandingkan nama kelas koneksi dengan nama kelas yang Anda buat. Jika keduanya sama, Anda dapat memperlakukannya sebagai instance dari sun.net.www.protocol.file.FileURLConnection daripada gagal jika nama kelasnya berubah.
William Deans
1
@Christian Anda dapat menghindari FileURLConnection melakukan sesuatu seperti ini: if ( ... instanceof JarURLConnecton) { ... } else { // Asume that the Connection is valid and points to a File }Ini yang saya lakukan pada kode saya untuk mencari kelas beranotasi JPA
Zardoz89
15

Tanpa menggunakan perpustakaan tambahan:

package test;

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) throws Exception{
        List<Class> classes = getClasses(Test.class.getClassLoader(),"test");
        for(Class c:classes){
            System.out.println("Class: "+c);
        }
    }

    public static List<Class> getClasses(ClassLoader cl,String pack) throws Exception{

        String dottedPackage = pack.replaceAll("[/]", ".");
        List<Class> classes = new ArrayList<Class>();
        URL upackage = cl.getResource(pack);

        DataInputStream dis = new DataInputStream((InputStream) upackage.getContent());
        String line = null;
        while ((line = dis.readLine()) != null) {
            if(line.endsWith(".class")) {
               classes.add(Class.forName(dottedPackage+"."+line.substring(0,line.lastIndexOf('.'))));
            }
        }
        return classes;
    }
}
Williams López
sumber
Ketika saya menjalankan ini dalam JAR, upackageadalah null... :(
Christian
Untuk paket "com.mycompany.beans", ganti "test" dengan "com / mycompany / beans"
James Jithin
4
Saya mendapatkan nol saat menggunakan kode ini. Tampaknya hanya berfungsi jika toples Anda dapat dieksekusi
Alao
jika Anda memperoleh nama paket dari String pack = getPackage().getName();, maka Anda harus menambahkanpack = pack.replaceAll("[.]", "/");
user2682877
13

Secara umum pemuat kelas tidak memungkinkan untuk memindai semua kelas pada classpath. Tetapi biasanya satu-satunya kelas loader yang digunakan adalah UrlClassLoader dari mana kita dapat mengambil daftar direktori dan file jar (lihat getURLs ) dan buka satu per satu untuk mendaftar kelas yang tersedia. Pendekatan ini, yang disebut pemindaian jalur kelas, diimplementasikan dalam Pemindaian dan Refleksi .

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 prosesor anotasi yang akan mengumpulkan semua kelas beranotasi pada waktu kompilasi dan membangun file indeks untuk penggunaan runtime. Mekanisme ini diterapkan di perpustakaan ClassIndex :

// package-info.java
@IndexSubclasses
package my.package;

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

Perhatikan bahwa tidak ada pengaturan tambahan yang diperlukan karena pemindaian sepenuhnya otomatis berkat Java compiler yang secara otomatis menemukan prosesor yang ditemukan di classpath.

Sławek
sumber
Apakah ini menemukan kelas yang dikemas dalam Jar? Sepertinya itu tidak berhasil untuk saya.
asgs
alat apa yang Anda coba gunakan?
Sławek
Saya menggunakan lib Reflections. Tapi saya berhasil setelah mengikuti solusi yang disebutkan oleh @Alexander Blomskøld untuk versi terbaru lib ini.
asgs
Hai, saya menggunakan eclipse dan tidak bisa membuatnya berfungsi, ClassIndex.getPackageClasses ("my.package") mengembalikan peta kosong
Juan
11

Mekanisme yang paling kuat untuk mendaftar semua kelas dalam paket yang diberikan saat ini adalah ClassGraph , karena menangani sederet mungkin mekanisme spesifikasi classpath , termasuk sistem modul JPMS yang baru. (Akulah penulisnya.)

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

Begini cara saya melakukannya. Saya memindai semua subfolder (sub paket) dan saya tidak mencoba memuat kelas anonim:

   /**
   * Attempts to list all the classes in the specified package as determined
   * by the context class loader, recursively, avoiding anonymous classes
   * 
   * @param pckgname
   *            the package name to search
   * @return a list of classes that exist within that package
   * @throws ClassNotFoundException
   *             if something went wrong
   */
  private static List<Class> getClassesForPackage(String pckgname) throws ClassNotFoundException {
      // This will hold a list of directories matching the pckgname. There may be more than one if a package is split over multiple jars/paths
      ArrayList<File> directories = new ArrayList<File>();
      String packageToPath = pckgname.replace('.', '/');
      try {
          ClassLoader cld = Thread.currentThread().getContextClassLoader();
          if (cld == null) {
              throw new ClassNotFoundException("Can't get class loader.");
          }

          // Ask for all resources for the packageToPath
          Enumeration<URL> resources = cld.getResources(packageToPath);
          while (resources.hasMoreElements()) {
              directories.add(new File(URLDecoder.decode(resources.nextElement().getPath(), "UTF-8")));
          }
      } catch (NullPointerException x) {
          throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Null pointer exception)");
      } catch (UnsupportedEncodingException encex) {
          throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Unsupported encoding)");
      } catch (IOException ioex) {
          throw new ClassNotFoundException("IOException was thrown when trying to get all resources for " + pckgname);
      }

      ArrayList<Class> classes = new ArrayList<Class>();
      // For every directoryFile identified capture all the .class files
      while (!directories.isEmpty()){
          File directoryFile  = directories.remove(0);             
          if (directoryFile.exists()) {
              // Get the list of the files contained in the package
              File[] files = directoryFile.listFiles();

              for (File file : files) {
                  // we are only interested in .class files
                  if ((file.getName().endsWith(".class")) && (!file.getName().contains("$"))) {
                      // removes the .class extension
                      int index = directoryFile.getPath().indexOf(packageToPath);
                      String packagePrefix = directoryFile.getPath().substring(index).replace('/', '.');;                          
                    try {                  
                      String className = packagePrefix + '.' + file.getName().substring(0, file.getName().length() - 6);                            
                      classes.add(Class.forName(className));                                
                    } catch (NoClassDefFoundError e)
                    {
                      // do nothing. this class hasn't been found by the loader, and we don't care.
                    }
                  } else if (file.isDirectory()){ // If we got to a subdirectory
                      directories.add(new File(file.getPath()));                          
                  }
              }
          } else {
              throw new ClassNotFoundException(pckgname + " (" + directoryFile.getPath() + ") does not appear to be a valid package");
          }
      }
      return classes;
  }  
Nadav B
sumber
4

Saya mengumpulkan proyek github sederhana yang memecahkan masalah ini:

https://github.com/ddopson/java-class-enumerator

Ini seharusnya berfungsi untuk KEDUA classpath berbasis file DAN untuk file jar.

Jika Anda menjalankan 'make' setelah memeriksa proyek itu akan mencetak ini:

 Cleaning...
rm -rf build/
 Building...
javac -d build/classes src/pro/ddopson/ClassEnumerator.java src/test/ClassIShouldFindOne.java src/test/ClassIShouldFindTwo.java src/test/subpkg/ClassIShouldFindThree.java src/test/TestClassEnumeration.java
 Making JAR Files...
jar cf build/ClassEnumerator_test.jar -C build/classes/ . 
jar cf build/ClassEnumerator.jar -C build/classes/ pro
 Running Filesystem Classpath Test...
java -classpath build/classes test.TestClassEnumeration
ClassDiscovery: Package: 'test' becomes Resource: 'file:/Users/Dopson/work/other/java-class-enumeration/build/classes/test'
ClassDiscovery: Reading Directory '/Users/Dopson/work/other/java-class-enumeration/build/classes/test'
ClassDiscovery: FileName 'ClassIShouldFindOne.class'  =>  class 'test.ClassIShouldFindOne'
ClassDiscovery: FileName 'ClassIShouldFindTwo.class'  =>  class 'test.ClassIShouldFindTwo'
ClassDiscovery: FileName 'subpkg'  =>  class 'null'
ClassDiscovery: Reading Directory '/Users/Dopson/work/other/java-class-enumeration/build/classes/test/subpkg'
ClassDiscovery: FileName 'ClassIShouldFindThree.class'  =>  class 'test.subpkg.ClassIShouldFindThree'
ClassDiscovery: FileName 'TestClassEnumeration.class'  =>  class 'test.TestClassEnumeration'
 Running JAR Classpath Test...
java -classpath build/ClassEnumerator_test.jar  test.TestClassEnumeration
ClassDiscovery: Package: 'test' becomes Resource: 'jar:file:/Users/Dopson/work/other/java-class-enumeration/build/ClassEnumerator_test.jar!/test'
ClassDiscovery: Reading JAR file: '/Users/Dopson/work/other/java-class-enumeration/build/ClassEnumerator_test.jar'
ClassDiscovery: JarEntry 'META-INF/'  =>  class 'null'
ClassDiscovery: JarEntry 'META-INF/MANIFEST.MF'  =>  class 'null'
ClassDiscovery: JarEntry 'pro/'  =>  class 'null'
ClassDiscovery: JarEntry 'pro/ddopson/'  =>  class 'null'
ClassDiscovery: JarEntry 'pro/ddopson/ClassEnumerator.class'  =>  class 'null'
ClassDiscovery: JarEntry 'test/'  =>  class 'null'
ClassDiscovery: JarEntry 'test/ClassIShouldFindOne.class'  =>  class 'test.ClassIShouldFindOne'
ClassDiscovery: JarEntry 'test/ClassIShouldFindTwo.class'  =>  class 'test.ClassIShouldFindTwo'
ClassDiscovery: JarEntry 'test/subpkg/'  =>  class 'null'
ClassDiscovery: JarEntry 'test/subpkg/ClassIShouldFindThree.class'  =>  class 'test.subpkg.ClassIShouldFindThree'
ClassDiscovery: JarEntry 'test/TestClassEnumeration.class'  =>  class 'test.TestClassEnumeration'
 Tests Passed. 

Lihat juga jawaban saya yang lain

Dave Dopson
sumber
4

Ya menggunakan beberapa API Anda bisa, di sini adalah bagaimana saya suka melakukannya, menghadapi masalah ini yang saya gunakan hibernate core & harus menemukan kelas yang mana dijelaskan dengan penjelasan tertentu.

Jadikan ini anotasi khusus dengan menggunakan mana Anda akan menandai kelas mana yang ingin Anda ambil.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EntityToBeScanned {

}

Kemudian tandai kelas Anda dengan itu seperti

@EntityToBeScanned 
public MyClass{

}

Buat kelas utilitas ini yang memiliki metode berikut

public class ClassScanner {

    public static Set<Class<?>> allFoundClassesAnnotatedWithEntityToBeScanned(){
        Reflections reflections = new Reflections(".*");
        Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(EntityToBeScanned.class);
        return annotated;
    }

}

Panggil metode allFoundClassesAnnotatedWithEntityToBeScanned () untuk mendapatkan Set of Classes yang ditemukan.

Anda akan membutuhkan lib yang diberikan di bawah ini

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>21.0</version>
    </dependency>
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.22.0-CR1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.10</version>
</dependency>
Sujal Mandal
sumber
3

Anda perlu mencari setiap entri pemuat kelas di jalur kelas:

    String pkg = "org/apache/commons/lang";
    ClassLoader cl = ClassLoader.getSystemClassLoader();
    URL[] urls = ((URLClassLoader) cl).getURLs();
    for (URL url : urls) {
        System.out.println(url.getFile());
        File jar = new File(url.getFile());
        // ....
    }   

Jika entri adalah direktori, cari saja di subdirektori yang tepat:

if (jar.isDirectory()) {
    File subdir = new File(jar, pkg);
    if (!subdir.exists())
        continue;
    File[] files = subdir.listFiles();
    for (File file : files) {
        if (!file.isFile())
            continue;
        if (file.getName().endsWith(".class"))
            System.out.println("Found class: "
                    + file.getName().substring(0,
                            file.getName().length() - 6));
    }
}   

Jika entri adalah file, dan itu jar, periksa entri ZIP itu:

else {
    // try to open as ZIP
    try {
        ZipFile zip = new ZipFile(jar);
        for (Enumeration<? extends ZipEntry> entries = zip
                .entries(); entries.hasMoreElements();) {
            ZipEntry entry = entries.nextElement();
            String name = entry.getName();
            if (!name.startsWith(pkg))
                continue;
            name = name.substring(pkg.length() + 1);
            if (name.indexOf('/') < 0 && name.endsWith(".class"))
                System.out.println("Found class: "
                        + name.substring(0, name.length() - 6));
        }
    } catch (ZipException e) {
        System.out.println("Not a ZIP: " + e.getMessage());
    } catch (IOException e) {
        System.err.println(e.getMessage());
    }
}

Sekarang setelah Anda memiliki semua paket nama yang ada dalam paket, Anda dapat mencoba memuatnya dengan refleksi dan menganalisis apakah mereka adalah kelas atau antarmuka, dll.

Pelaut Danubia
sumber
Apa yang akan Anda masukkan untuk paket dalam file Jar?
Kyle Bridenstine
Contoh ini tidak akan melalui sub-paket. Mungkin itu menarik bagi beberapa ... @ mr-tea Cukup tentukan paket yang Anda cari. Saya memasukkan ini ke dalam proyek, menentukan paket pengujian di dalam proyek itu, menyusun dan mengemasnya dan menyebut contoh dari metode utama JAR. Bekerja seperti pesona. :)
Christian
3

Saya sudah mencoba menggunakan perpustakaan Reflections, tetapi ada beberapa masalah dalam menggunakannya, dan ada terlalu banyak toples yang harus saya masukkan hanya untuk mendapatkan kelas-kelas pada sebuah paket.

Saya akan memposting solusi yang saya temukan dalam pertanyaan duplikat ini: Bagaimana cara mendapatkan semua nama kelas dalam sebuah paket?

The jawaban ditulis oleh sp00m ; Saya telah menambahkan beberapa koreksi untuk membuatnya berfungsi:

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;

public final class ClassFinder {

    private final static char DOT = '.';
    private final static char SLASH = '/';
    private final static String CLASS_SUFFIX = ".class";
    private final static String BAD_PACKAGE_ERROR = "Unable to get resources from path '%s'. Are you sure the given '%s' package exists?";

    public final static List<Class<?>> find(final String scannedPackage) {
        final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        final String scannedPath = scannedPackage.replace(DOT, SLASH);
        final Enumeration<URL> resources;
        try {
            resources = classLoader.getResources(scannedPath);
        } catch (IOException e) {
            throw new IllegalArgumentException(String.format(BAD_PACKAGE_ERROR, scannedPath, scannedPackage), e);
        }
        final List<Class<?>> classes = new LinkedList<Class<?>>();
        while (resources.hasMoreElements()) {
            final File file = new File(resources.nextElement().getFile());
            classes.addAll(find(file, scannedPackage));
        }
        return classes;
    }

    private final static List<Class<?>> find(final File file, final String scannedPackage) {
        final List<Class<?>> classes = new LinkedList<Class<?>>();
        if (file.isDirectory()) {
            for (File nestedFile : file.listFiles()) {
                classes.addAll(find(nestedFile, scannedPackage));
            }
        //File names with the $1, $2 holds the anonymous inner classes, we are not interested on them. 
        } else if (file.getName().endsWith(CLASS_SUFFIX) && !file.getName().contains("$")) {

            final int beginIndex = 0;
            final int endIndex = file.getName().length() - CLASS_SUFFIX.length();
            final String className = file.getName().substring(beginIndex, endIndex);
            try {
                final String resource = scannedPackage + DOT + className;
                classes.add(Class.forName(resource));
            } catch (ClassNotFoundException ignore) {
            }
        }
        return classes;
    }

}

Untuk menggunakannya cukup panggil metode find seperti sp00n yang disebutkan dalam contoh ini: Saya telah menambahkan pembuatan instance kelas jika diperlukan.

List<Class<?>> classes = ClassFinder.find("com.package");

ExcelReporting excelReporting;
for (Class<?> aClass : classes) {
    Constructor constructor = aClass.getConstructor();
    //Create an object of the class type
    constructor.newInstance();
    //...
}
Martín C
sumber
3

Saya baru saja menulis kelas util, itu termasuk metode tes, Anda dapat memiliki cek ~

IteratePackageUtil.java:

package eric.j2se.reflect;

import java.util.Set;

import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;

/**
 * an util to iterate class in a package,
 * 
 * @author eric
 * @date Dec 10, 2013 12:36:46 AM
 */
public class IteratePackageUtil {
    /**
     * <p>
     * Get set of all class in a specified package recursively. this only support lib
     * </p>
     * <p>
     * class of sub package will be included, inner class will be included,
     * </p>
     * <p>
     * could load class that use the same classloader of current class, can't load system packages,
     * </p>
     * 
     * @param pkg
     *            path of a package
     * @return
     */
    public static Set<Class<? extends Object>> getClazzSet(String pkg) {
        // prepare reflection, include direct subclass of Object.class
        Reflections reflections = new Reflections(new ConfigurationBuilder().setScanners(new SubTypesScanner(false), new ResourcesScanner())
                .setUrls(ClasspathHelper.forClassLoader(ClasspathHelper.classLoaders(new ClassLoader[0])))
                .filterInputsBy(new FilterBuilder().includePackage(pkg)));

        return reflections.getSubTypesOf(Object.class);
    }

    public static void test() {
        String pkg = "org.apache.tomcat.util";

        Set<Class<? extends Object>> clazzSet = getClazzSet(pkg);
        for (Class<? extends Object> clazz : clazzSet) {
            System.out.println(clazz.getName());
        }
    }

    public static void main(String[] args) {
        test();
    }
}
Eric Wang
sumber
3

Solusi Aleksander Blomskøld tidak bekerja untuk saya untuk tes parameter @RunWith(Parameterized.class)ketika menggunakan Maven. Tes diberi nama dengan benar dan juga tempat ditemukan tetapi tidak dieksekusi:

-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running some.properly.named.test.run.with.maven.SomeTest
Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.123 sec

Masalah serupa telah dilaporkan di sini .

Dalam kasus saya @Parametersadalah membuat instance dari setiap kelas dalam sebuah paket. Tes bekerja dengan baik ketika dijalankan secara lokal di IDE. Namun, ketika menjalankan Maven tidak ada kelas di mana ditemukan dengan solusi Aleksander Blomskøld.

Saya berhasil membuatnya dengan potongan berikut yang diilhami oleh komentar David Pärsson pada jawaban Aleksander Blomskøld:

Reflections reflections = new Reflections(new ConfigurationBuilder()
            .setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
            .addUrls(ClasspathHelper.forJavaClassPath()) 
            .filterInputsBy(new FilterBuilder()
            .include(FilterBuilder.prefix(basePackage))));

Set<Class<?>> subTypesOf = reflections.getSubTypesOf(Object.class);
Thorsten
sumber
3

Bagaimana dengan ini:

public static List<Class<?>> getClassesForPackage(final String pkgName) throws IOException, URISyntaxException {
    final String pkgPath = pkgName.replace('.', '/');
    final URI pkg = Objects.requireNonNull(ClassLoader.getSystemClassLoader().getResource(pkgPath)).toURI();
    final ArrayList<Class<?>> allClasses = new ArrayList<Class<?>>();

    Path root;
    if (pkg.toString().startsWith("jar:")) {
        try {
            root = FileSystems.getFileSystem(pkg).getPath(pkgPath);
        } catch (final FileSystemNotFoundException e) {
            root = FileSystems.newFileSystem(pkg, Collections.emptyMap()).getPath(pkgPath);
        }
    } else {
        root = Paths.get(pkg);
    }

    final String extension = ".class";
    try (final Stream<Path> allPaths = Files.walk(root)) {
        allPaths.filter(Files::isRegularFile).forEach(file -> {
            try {
                final String path = file.toString().replace('/', '.');
                final String name = path.substring(path.indexOf(pkgName), path.length() - extension.length());
                allClasses.add(Class.forName(name));
            } catch (final ClassNotFoundException | StringIndexOutOfBoundsException ignored) {
            }
        });
    }
    return allClasses;
}

Anda kemudian dapat membebani fungsi:

public static List<Class<?>> getClassesForPackage(final Package pkg) throws IOException, URISyntaxException {
    return getClassesForPackage(pkg.getName());
}

Jika Anda perlu mengujinya:

public static void main(final String[] argv) throws IOException, URISyntaxException {
    for (final Class<?> cls : getClassesForPackage("my.package")) {
        System.out.println(cls);
    }
    for (final Class<?> cls : getClassesForPackage(MyClass.class.getPackage())) {
        System.out.println(cls);
    }
}

Jika IDE Anda tidak memiliki pembantu impor:

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

Berhasil:

  • dari IDE Anda

  • untuk file JAR

  • tanpa ketergantungan eksternal

tirz
sumber
2

Hampir semua jawaban menggunakan Reflectionsatau membaca file kelas dari sistem file. Jika Anda mencoba membaca kelas dari sistem file, Anda mungkin mendapatkan kesalahan saat Anda mengemas aplikasi Anda sebagai JAR atau lainnya. Anda juga mungkin tidak ingin menggunakan perpustakaan terpisah untuk tujuan itu.

Berikut ini adalah pendekatan lain yang murni java dan tidak tergantung pada sistem file.

import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

public class PackageUtil {

    public static Collection<Class> getClasses(final String pack) throws Exception {
        final StandardJavaFileManager fileManager = ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null);
        return StreamSupport.stream(fileManager.list(StandardLocation.CLASS_PATH, pack, Collections.singleton(JavaFileObject.Kind.CLASS), false).spliterator(), false)
                .map(javaFileObject -> {
                    try {
                        final String[] split = javaFileObject.getName()
                                .replace(".class", "")
                                .replace(")", "")
                                .split(Pattern.quote(File.separator));

                        final String fullClassName = pack + "." + split[split.length - 1];
                        return Class.forName(fullClassName);
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }

                })
                .collect(Collectors.toCollection(ArrayList::new));
    }
}

Java 8 bukan keharusan . Anda dapat menggunakan loop sebagai ganti stream. Dan Anda bisa mengujinya seperti ini

public static void main(String[] args) throws Exception {
    final String pack = "java.nio.file"; // Or any other package
    PackageUtil.getClasses(pack).stream().forEach(System.out::println);
}
bhdrkn
sumber
1
Ini tidak terlalu berguna karena: perlu memiliki JDK untuk digunakan ToolProvider.getSystemJavaCompiler(), kode ini tidak memindai paket bersarang.
v.ladynev
Saya tidak bisa membuatnya bekerja dengan paket stoples eksternal
Enrico Giurin
1

Asalkan Anda tidak menggunakan loader kelas dinamis, Anda dapat mencari classpath dan untuk setiap entri mencari direktori atau file JAR.

Lawrence Dol
sumber
1

Layak disebut

Jika Anda ingin memiliki daftar semua kelas di bawah beberapa paket, Anda dapat menggunakan Reflectioncara berikut:

List<Class> myTypes = new ArrayList<>();

Reflections reflections = new Reflections("com.package");
for (String s : reflections.getStore().get(SubTypesScanner.class).values()) {
    myTypes.add(Class.forName(s));
}

Ini akan membuat daftar kelas yang nantinya bisa Anda gunakan sesuai keinginan.

Maroun
sumber
1

Sangat mungkin, tetapi tanpa perpustakaan tambahan seperti Reflectionsitu sulit ...
Ini sulit karena Anda belum memiliki instrumen lengkap untuk mendapatkan nama kelas.
Dan, saya mengambil kode ClassFinderkelas saya :

package play.util;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Created by LINKOR on 26.05.2017 in 15:12.
 * Date: 2017.05.26
 */
public class FileClassFinder {
private JarFile file;
private boolean trouble;
public FileClassFinder(String filePath) {
    try {
        file = new JarFile(filePath);
    } catch (IOException e) {
        trouble = true;
    }
}

public List<String> findClasses(String pkg) {
    ArrayList<String> classes = new ArrayList<>();
    Enumeration<JarEntry> entries = file.entries();
    while (entries.hasMoreElements()) {
        JarEntry cls = entries.nextElement();
        if (!cls.isDirectory()) {
            String fileName = cls.getName();
            String className = fileName.replaceAll("/",         ".").replaceAll(File.pathSeparator, ".").substring(0, fileName.lastIndexOf('.'));
            if (className.startsWith(pkg)) classes.add(className.substring(pkg.length() + 1));
        }
    }
    return classes;
}
}
Muskovets
sumber
0

Berdasarkan jawaban @ Staale , dan dalam upaya untuk tidak bergantung pada perpustakaan pihak ketiga, saya akan menerapkan pendekatan Sistem File dengan memeriksa lokasi fisik paket pertama dengan:

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
...
Class<?>[] foundClasses = new Class<?>[0];
final ArrayList<Class<?>> foundClassesDyn = new ArrayList<Class<?>>();

new java.io.File(
    klass.getResource(
        "/" + curPackage.replace( "." , "/")
    ).getFile()
).listFiles(
    new java.io.FileFilter() {
        public boolean accept(java.io.File file) {
            final String classExtension = ".class";

            if ( file.isFile()
                && file.getName().endsWith(classExtension)
                // avoid inner classes
                && ! file.getName().contains("$") )
            {
                try {
                    String className = file.getName();
                    className = className.substring(0, className.length() - classExtension.length());
                    foundClassesDyn.add( Class.forName( curPackage + "." + className ) );
                } catch (ClassNotFoundException e) {
                    e.printStackTrace(System.out);
                }
            }

            return false;
        }
    }
);

foundClasses = foundClassesDyn.toArray(foundClasses);
Komunitas
sumber
0

Jika Anda hanya ingin memuat grup kelas terkait, maka Spring dapat membantu Anda.

Spring dapat membuat daftar atau peta semua kelas yang mengimplementasikan antarmuka yang diberikan dalam satu baris kode. Daftar atau peta akan berisi contoh semua kelas yang mengimplementasikan antarmuka itu.

Yang sedang berkata, sebagai alternatif untuk memuat daftar kelas dari sistem file, alih-alih hanya mengimplementasikan antarmuka yang sama di semua kelas yang ingin Anda muat, terlepas dari paket dan gunakan Spring untuk memberi Anda contoh dari semuanya. Dengan begitu, Anda dapat memuat (dan instantiate) semua kelas yang Anda inginkan terlepas dari paket apa yang mereka masuki.

Di sisi lain, jika memiliki semuanya dalam satu paket adalah yang Anda inginkan, maka cukup sediakan semua kelas dalam paket tersebut untuk mengimplementasikan antarmuka yang diberikan.

Rodney P. Barbati
sumber
0

java polos: FindAllClassesUsingPlainJavaReflectionTest.java

@Slf4j
class FindAllClassesUsingPlainJavaReflectionTest {

  private static final Function<Throwable, RuntimeException> asRuntimeException = throwable -> {
    log.error(throwable.getLocalizedMessage());
    return new RuntimeException(throwable);
  };

  private static final Function<String, Collection<Class<?>>> findAllPackageClasses = basePackageName -> {

    Locale locale = Locale.getDefault();
    Charset charset = StandardCharsets.UTF_8;
    val fileManager = ToolProvider.getSystemJavaCompiler()
                                  .getStandardFileManager(/* diagnosticListener */ null, locale, charset);

    StandardLocation location = StandardLocation.CLASS_PATH;
    JavaFileObject.Kind kind = JavaFileObject.Kind.CLASS;
    Set<JavaFileObject.Kind> kinds = Collections.singleton(kind);
    val javaFileObjects = Try.of(() -> fileManager.list(location, basePackageName, kinds, /* recurse */ true))
                             .getOrElseThrow(asRuntimeException);

    String pathToPackageAndClass = basePackageName.replace(".", File.separator);
    Function<String, String> mapToClassName = s -> {
      String prefix = Arrays.stream(s.split(pathToPackageAndClass))
                            .findFirst()
                            .orElse("");
      return s.replaceFirst(prefix, "")
              .replaceAll(File.separator, ".");
    };

    return StreamSupport.stream(javaFileObjects.spliterator(), /* parallel */ true)
                        .filter(javaFileObject -> javaFileObject.getKind().equals(kind))
                        .map(FileObject::getName)
                        .map(fileObjectName -> fileObjectName.replace(".class", ""))
                        .map(mapToClassName)
                        .map(className -> Try.of(() -> Class.forName(className))
                                             .getOrElseThrow(asRuntimeException))
                        .collect(Collectors.toList());
  };

  @Test
  @DisplayName("should get classes recursively in given package")
  void test() {
    Collection<Class<?>> classes = findAllPackageClasses.apply(getClass().getPackage().getName());
    assertThat(classes).hasSizeGreaterThan(4);
    classes.stream().map(String::valueOf).forEach(log::info);
  }
}

PS: untuk menyederhanakan pelat boiler untuk menangani kesalahan, dll, saya gunakan di sini vavrdan lombokperpustakaan

implementasi lain dapat ditemukan di repo GitHub daggerok / java-reflection-find-annotated-class-or-methods saya

Maksim Kostromin
sumber
0

Saya tidak dapat menemukan pekerjaan pendek untuk sesuatu yang sangat sederhana. Jadi ini dia, saya membuatnya sendiri setelah bermain-main sebentar:

    Reflections reflections =
        new Reflections(new ConfigurationBuilder()
                .filterInputsBy(new FilterBuilder().includePackage(packagePath))
                .setUrls(ClasspathHelper.forPackage(packagePath))
                .setScanners(new SubTypesScanner(false)));

    Set<String> typeList = reflections.getAllTypes(); 
botenvouwer
sumber
0

Jika Anda berada di Spring-land, Anda dapat menggunakan PathMatchingResourcePatternResolver;

  PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
  Resource[] resources = resolver.getResources("classpath*:some/package/name/*.class");

    Arrays.asList(resources).forEach(r->{
        ...
    });
Hitam
sumber
-4

Itu tidak mungkin, karena semua kelas dalam paket mungkin tidak dimuat, sementara Anda selalu tahu paket kelas.

Marko
sumber