Memindai anotasi Java saat runtime [ditutup]

253

Apa cara terbaik mencari seluruh kelas untuk kelas beranotasi?

Saya sedang melakukan perpustakaan dan saya ingin memungkinkan pengguna untuk membubuhi keterangan kelas mereka, jadi ketika aplikasi Web dimulai saya perlu memindai seluruh classpath untuk anotasi tertentu.

Apakah Anda tahu perpustakaan atau fasilitas Java untuk melakukan ini?

Sunting: Saya sedang memikirkan sesuatu seperti fungsi baru untuk Layanan Web Java EE 5 atau EJB. Anda membubuhi keterangan kelas Anda dengan @WebServiceatau @EJBdan sistem menemukan kelas-kelas ini saat memuat sehingga mereka dapat diakses dari jarak jauh.

Alotor
sumber

Jawaban:

210

Gunakan org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

API

Penyedia komponen yang memindai classpath dari paket dasar. Ini kemudian berlaku kecualikan dan sertakan filter ke kelas yang dihasilkan untuk menemukan kandidat.

ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(<DO_YOU_WANT_TO_USE_DEFALT_FILTER>);

scanner.addIncludeFilter(new AnnotationTypeFilter(<TYPE_YOUR_ANNOTATION_HERE>.class));

for (BeanDefinition bd : scanner.findCandidateComponents(<TYPE_YOUR_BASE_PACKAGE_HERE>))
    System.out.println(bd.getBeanClassName());
Arthur Ronald
sumber
5
Terima kasih atas informasinya. Apakah Anda juga tahu cara memindai classpath untuk kelas yang bidangnya memiliki anotasi khusus?
Jelaskan
6
@Jabarkan Gunakan API refleksi Jawa. <YOUR_CLASS> .class.getFields () Untuk setiap bidang, aktifkan getAnnotation (<YOUR_ANNOTATION>)
Arthur Ronald
1
CATATAN: Jika Anda melakukan ini di dalam aplikasi Spring, Spring akan tetap mengevaluasi dan bertindak berdasarkan @Conditionalanotasi. Jadi, jika suatu kelas memiliki @Conditionalnilainya mengembalikan false, itu tidak akan dikembalikan oleh findCandidateComponents, bahkan jika itu cocok dengan filter pemindai. Ini melempar saya hari ini - saya akhirnya menggunakan solusi Jonathan di bawah ini sebagai gantinya.
Adam Burley
1
@ArthurRonald Maaf, Arthur. Maksud saya, BeanDefinitionobjek tidak menyediakan cara untuk mendapatkan kelas secara langsung. Hal terdekat tampaknya adalah getBeanClassNameyang mengembalikan nama kelas yang berkualifikasi penuh, tetapi perilaku yang tepat dari metode ini tidak jelas. Juga, tidak jelas pemuat kelas mana yang ditemukan.
Maks
3
@ Max Coba ini: Class<?> cl = Class.forName(beanDef.getBeanClassName()); farenda.com/spring/find-annotated-classes
James Watkins
149

Dan solusi lain adalah refleksi Google .

Ulasan cepat:

  • Solusi pegas adalah cara untuk pergi jika Anda menggunakan Pegas. Kalau tidak, itu adalah ketergantungan besar.
  • Menggunakan ASM secara langsung agak rumit.
  • Menggunakan Java Assist secara langsung juga kikuk.
  • Annovention sangat ringan dan nyaman. Belum ada integrasi pakar.
  • Refleksi Google menarik koleksi Google. Mengindeks semuanya dan kemudian super cepat.
Jonathan
sumber
43
new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class). disebut-sebut.
zapp
4
apakah saya perlu menentukan nama paket? wildcard? apa untuk semua kelas di classpath?
Sunnyday
1
Berhati-hatilah karena masih belum ada kemajuan pada dukungan Java 9: github.com/ronmamo/reflections/issues/186
Vadzim
perpustakaan org.reflections tidak berfungsi di bawah java 13 (mungkin lebih awal juga). Pertama kali dipanggil sepertinya tidak masalah. Instansiasi dan penggunaan berikutnya gagal mengatakan url pencarian tidak dikonfigurasikan.
Evvo
44

Anda dapat menemukan kelas dengan penjelasan apa pun yang diberikan dengan ClassGraph , serta mencari kriteria menarik lainnya, misalnya kelas yang mengimplementasikan antarmuka yang diberikan. (Penafian, saya adalah penulis dari ClassGraph.) ClassGraph dapat membangun representasi abstrak dari seluruh grafik kelas (semua kelas, penjelasan, metode, parameter metode, dan bidang) dalam memori, untuk semua kelas di classpath, atau untuk kelas di paket yang masuk daftar putih, dan Anda dapat meminta grafik kelas itu sesuai keinginan. ClassGraph mendukung lebih banyak mekanisme spesifikasi classpath dan classloader daripada pemindai lain, dan juga bekerja dengan mulus dengan sistem modul JPMS baru, jadi jika Anda mendasarkan kode Anda pada ClassGraph, kode Anda akan portabel secara maksimal. Lihat API di sini.

Luke Hutchison
sumber
1
Apakah ini perlu Java 8 untuk dijalankan?
David George
1
Diperbarui untuk menggunakan Java7, tidak ada masalah. Hapus saja pengumuman dan konversikan fungsi untuk menggunakan kelas dalam anonim. Saya suka gaya file 1. Kode ini bagus dan bersih, jadi meskipun tidak mendukung beberapa hal yang saya ingin (kelas + anotasi pada saat yang sama) Saya pikir itu akan sangat mudah untuk ditambahkan. Kerja bagus! Jika seseorang tidak dapat melakukan pekerjaan untuk memodifikasi v7, mereka mungkin harus ikut Reflections. Juga, jika Anda menggunakan jambu biji / etc dan ingin mengganti koleksi, semudah pie. Komentar yang bagus di dalam juga.
Andrew Backer
2
@Alexandros terima kasih, Anda harus memeriksa ClassGraph, ini lebih baik daripada FastClasspathScanner.
Luke Hutchison
2
@AndrewBacker ClassGraph (versi baru FastClasspathScanner) memiliki dukungan penuh untuk operasi Boolean, melalui filter atau mengatur operasi. Lihat contoh kode di sini: github.com/classgraph/classgraph/wiki/Code-examples
Luke Hutchison
1
@ Lukas Hutchison Sudah menggunakan ClassGraph. Membantu saya dengan migrasi ke Java 10. Perpustakaan yang sangat berguna.
Alexandros
25

Jika Anda menginginkan bobot yang sangat ringan (tanpa dependensi, API sederhana, file jar 15 kb) dan solusi yang sangat cepat , lihat annotation-detectordi https://github.com/rmuller/infomas-asl

Penafian: Saya penulis.

ruller
sumber
20

Anda dapat menggunakan Java Pluggable Annotation Processing API untuk menulis prosesor anotasi yang akan dieksekusi selama proses kompilasi dan akan mengumpulkan semua kelas beranotasi dan membuat file indeks untuk penggunaan runtime.

Ini adalah cara tercepat untuk melakukan penemuan kelas beranotasi karena Anda tidak perlu memindai classpath Anda saat runtime, yang biasanya operasi sangat lambat. Juga pendekatan ini berfungsi dengan classloader apa pun dan tidak hanya dengan URLClassLoaders yang biasanya didukung oleh pemindai runtime.

Mekanisme di atas sudah diterapkan di perpustakaan ClassIndex .

Untuk menggunakannya, beri anotasi anotasi khusus dengan meta-anotasi @IndexAnnotated . Ini akan membuat pada waktu kompilasi suatu file indeks: META-INF / annotations / com / test / YourCustomAnnotation daftar semua kelas yang dijelaskan. Anda dapat mengakses indeks saat runtime dengan menjalankan:

ClassIndex.getAnnotated(com.test.YourCustomAnnotation.class)
Sławek
sumber
5

Apakah sudah terlambat untuk menjawab. Saya akan mengatakan, lebih baik menggunakan Libraries seperti ClassPathScanningCandidateComponentProvider atau seperti Scannotations

Tetapi bahkan setelah seseorang ingin mencobanya dengan classLoader, saya telah menulis sendiri untuk mencetak anotasi dari kelas dalam satu paket:

public class ElementScanner {

public void scanElements(){
    try {
    //Get the package name from configuration file
    String packageName = readConfig();

    //Load the classLoader which loads this class.
    ClassLoader classLoader = getClass().getClassLoader();

    //Change the package structure to directory structure
    String packagePath  = packageName.replace('.', '/');
    URL urls = classLoader.getResource(packagePath);

    //Get all the class files in the specified URL Path.
    File folder = new File(urls.getPath());
    File[] classes = folder.listFiles();

    int size = classes.length;
    List<Class<?>> classList = new ArrayList<Class<?>>();

    for(int i=0;i<size;i++){
        int index = classes[i].getName().indexOf(".");
        String className = classes[i].getName().substring(0, index);
        String classNamePath = packageName+"."+className;
        Class<?> repoClass;
        repoClass = Class.forName(classNamePath);
        Annotation[] annotations = repoClass.getAnnotations();
        for(int j =0;j<annotations.length;j++){
            System.out.println("Annotation in class "+repoClass.getName()+ " is "+annotations[j].annotationType().getName());
        }
        classList.add(repoClass);
    }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

/**
 * Unmarshall the configuration file
 * @return
 */
public String readConfig(){
    try{
        URL url = getClass().getClassLoader().getResource("WEB-INF/config.xml");
        JAXBContext jContext = JAXBContext.newInstance(RepositoryConfig.class);
         Unmarshaller um =  jContext.createUnmarshaller();
         RepositoryConfig rc = (RepositoryConfig) um.unmarshal(new File(url.getFile()));
         return rc.getRepository().getPackageName();
        }catch(Exception e){
            e.printStackTrace();
        }
    return null;

}
}

Dan dalam File config, Anda meletakkan nama paket dan menghapusnya ke kelas.

kenobiwan
sumber
3

Dengan Spring Anda juga bisa menulis berikut ini menggunakan kelas AnnotationUtils. yaitu:

Class<?> clazz = AnnotationUtils.findAnnotationDeclaringClass(Target.class, null);

Untuk detail lebih lanjut dan semua metode yang berbeda, periksa dokumen resmi: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html

pembuat sihir
sumber
4
Gagasan yang bagus, tetapi jika Anda meletakkan nullnilai sebagai parameter kedua (yang mendefinisikan kelas, di mana hierarki warisan Spring akan memindai kelas yang menggunakan Anotasi), Anda akan mendapatkan balasan null, sesuai dengan implementasinya.
Jonashackt
3

Ada komentar yang bagus dari zapp yang memenuhi semua jawaban itu:

new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class)
Zon
sumber
2

Classloader API tidak memiliki metode "enumerate", karena pemuatan kelas adalah aktivitas "sesuai permintaan" - Anda biasanya memiliki ribuan kelas di classpath Anda, hanya sebagian kecil yang akan diperlukan (rt.jar sendiri adalah 48MB saat ini!).

Jadi, bahkan jika Anda dapat menghitung semua kelas, ini akan sangat memakan waktu dan memori.

Pendekatan sederhananya adalah dengan mendaftarkan kelas-kelas yang bersangkutan dalam file setup (xml atau apa pun yang sesuai dengan keinginan Anda); jika Anda ingin melakukan ini secara otomatis, batasi diri Anda pada satu JAR atau satu direktori kelas.

mfx
sumber
2

Google Reflection jika Anda ingin menemukan antarmuka juga.

Spring ClassPathScanningCandidateComponentProvidertidak menemukan antarmuka.

madhu_karnati
sumber
1

Google Refleksi tampaknya jauh lebih cepat daripada Spring. Menemukan permintaan fitur ini yang mengatasi perbedaan ini: http://www.opensaga.org/jira/browse/OS-738

Ini adalah alasan untuk menggunakan Refleksi karena waktu startup aplikasi saya sangat penting selama pengembangan. Refleksi tampaknya juga sangat mudah digunakan untuk kasus penggunaan saya (temukan semua pelaksana antarmuka).

Martin Aubele
sumber
1
Jika Anda melihat masalah JIRA, ada komentar di sana bahwa mereka pindah dari Refleksi karena masalah stabilitas.
Wim Deblauwe
1

Jika Anda mencari alternatif untuk refleksi, saya ingin merekomendasikan Panda Utilities - AnnotationsScanner . Ini adalah pemindai yang bebas-Jambu (memiliki ~ 3 MB, Panda Utilities memiliki ~ 200kb) berdasarkan kode sumber perpustakaan refleksi.

Ini juga didedikasikan untuk pencarian berbasis masa depan. Jika Anda ingin memindai beberapa kali menyertakan sumber atau bahkan menyediakan API, yang memungkinkan seseorang memindai classpath saat ini, AnnotationsScannerProcesssemua cache diambil ClassFiles, sehingga sangat cepat.

Contoh AnnotationsScannerpenggunaan sederhana :

AnnotationsScanner scanner = AnnotationsScanner.createScanner()
        .includeSources(ExampleApplication.class)
        .build();

AnnotationsScannerProcess process = scanner.createWorker()
        .addDefaultProjectFilters("net.dzikoysk")
        .fetch();

Set<Class<?>> classes = process.createSelector()
        .selectTypesAnnotatedWith(AnnotationTest.class);
dzikoysk
sumber
1

Musim semi memiliki sesuatu yang disebut AnnotatedTypeScannerkelas.
Kelas ini digunakan secara internal

ClassPathScanningCandidateComponentProvider

Kelas ini memiliki kode untuk pemindaian aktual sumber daya classpath . Ini dilakukan dengan menggunakan metadata kelas yang tersedia saat runtime.

Seseorang dapat dengan mudah memperluas kelas ini atau menggunakan kelas yang sama untuk pemindaian. Di bawah ini adalah definisi konstruktor.

/**
     * Creates a new {@link AnnotatedTypeScanner} for the given annotation types.
     * 
     * @param considerInterfaces whether to consider interfaces as well.
     * @param annotationTypes the annotations to scan for.
     */
    public AnnotatedTypeScanner(boolean considerInterfaces, Class<? extends Annotation>... annotationTypes) {

        this.annotationTypess = Arrays.asList(annotationTypes);
        this.considerInterfaces = considerInterfaces;
    }
swayamraina
sumber