Java ek açıklamalarını çalışma zamanında tarama [kapalı]


253

Açıklamalı bir sınıf için tüm sınıfyolunu aramanın en iyi yolu nedir?

Bir kütüphane yapıyorum ve kullanıcıların kendi sınıflarına açıklama eklemesine izin vermek istiyorum, bu yüzden Web uygulaması başladığında tüm sınıf yolunu belirli açıklamalar için taramaya ihtiyacım var.

Bunu yapmak için bir kütüphane veya bir Java tesisi biliyor musunuz?

Düzenleme: Java EE 5 Web Hizmetleri veya EJB'ler için yeni işlevsellik gibi bir şey düşünüyorum. Sınıfınıza @WebServiceveya ile ek açıklama eklersiniz @EJBve sistem bu sınıfları yüklerken uzaktan erişebilmeleri için bulur.

Yanıtlar:


210

Org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider kullanın

API

Bir temel paketten sınıfyolunu tarayan bir bileşen sağlayıcısı. Daha sonra, adayları bulmak için sonuçta elde edilen sınıflara hariç tut ve filtreler ekler.

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());

5
Bilgi için teşekkürler. Alanları özel ek açıklama içeren sınıflar için sınıf yolunu nasıl tarayacağınızı da biliyor musunuz?
Javatar

6
@Javatar Java'nın yansıma API'sını kullanın. <YOUR_CLASS> .class.getFields () Her alan için getAnnotation'ı (<YOUR_ANNOTATION>) çağırın
Arthur Ronald

1
NOT: Bunu bir Bahar uygulaması içinde yapıyorsanız, Bahar yine de @Conditionalek açıklamalara göre değerlendirilecek ve hareket edecektir . Dolayısıyla, bir sınıfın @Conditionaldeğeri yanlış döndürüyorsa findCandidateComponents, tarayıcının filtresiyle eşleşse bile, tarafından döndürülmez . Bu beni bugün attı - bunun yerine Jonathan'ın çözümünü kullandım.
Adam Burley

1
@ArthurRonald Üzgünüm Arthur. Yani BeanDefinitionnesnenin sınıfı doğrudan almak için bir yol sağlamadığı anlamına geliyor . En yakın şey getBeanClassName, tam nitelikli bir sınıf adı döndüren gibi görünüyor , ancak bu yöntemin tam davranışı net değil. Ayrıca, sınıfın hangi sınıf yükleyicide bulunduğu açık değildir.
Max

3
@Max Şunu deneyin: Class<?> cl = Class.forName(beanDef.getBeanClassName()); farenda.com/spring/find-annotated-classes
James Watkins

149

Bir diğer çözüm de Google'ın yansımalarıdır .

Hızlı inceleme:

  • Spring kullanıyorsanız, Spring çözümü kullanmanız gereken yoldur. Aksi takdirde büyük bir bağımlılık.
  • ASM'yi doğrudan kullanmak biraz hantal.
  • Java Assist'i doğrudan kullanmak da karmaşıktır.
  • Ek açıklama süper hafif ve kullanışlıdır. Henüz maven entegrasyonu yok.
  • Google yansımaları Google koleksiyonlarından çekilir. Her şeyi dizine ekler ve sonra süper hızlıdır.

43
new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class). en iyisi.
zapp

4
bir paket adı belirtmem gerekir mi? joker? sınıf yolundaki tüm sınıflar için ne olacak?
Sunnyday

1
Java 9 desteğinde hala ilerleme kaydedilmediğine dikkat edin: github.com/ronmamo/reflections/issues/186
Vadzim

org.reflections kütüphanesi java 13 altında çalışmıyor olabilir (belki daha erken de olabilir). İlk çağrıldığında iyi görünüyor. sonraki örneklemeler ve kullanımlar, arama URL'lerinin yapılandırılmadığını söyleyerek başarısız olur.
Evvo

44

ClassGraph ile herhangi bir ek açıklama içeren sınıfları bulabilir , ayrıca diğer ilgili kriterleri, örneğin belirli bir arabirimi uygulayan sınıfları arayabilirsiniz . (Feragat, ben ClassGraph yazarım.) ClassGraph bellekte, sınıf yolundaki tüm sınıflar veya sınıflar için tüm sınıf grafiğinin (tüm sınıflar, detaylandırmalar, yöntemler, yöntem parametreleri ve alanlar) soyut bir temsilini oluşturabilir beyaz listeye eklenmiş paketleri ve istediğiniz sınıf grafiğini sorgulayabilirsiniz. ClassGraph, diğer tarayıcılardan daha fazla sınıf yolu belirtim mekanizmasını ve sınıf yükleyiciyi destekler ve ayrıca yeni JPMS modül sistemi ile sorunsuz bir şekilde çalışır, bu nedenle kodunuzu ClassGraph'a dayandırırsanız, kodunuz maksimum taşınabilir olacaktır. Buradaki API'ya bakın.


1
Bunun çalışması için Java 8 gerekiyor mu?
David George

1
Java7 kullanmak için güncellendi, sorun değil. Sadece açıklamaları kaldırın ve anonim iç sınıfları kullanmak için işlevleri dönüştürün. 1 dosya stilini seviyorum. Kod güzel bir temiz, bu yüzden ben istiyorum birkaç şey (sınıf + ek açıklama aynı anda) desteklemese bile ben eklemek oldukça lanet kolay olacağını düşünüyorum. Harika iş! Birisi v7 için değişiklik yapmak için işi yapamazsa, muhtemelen gitmelidir Reflections. Ayrıca, guava / etc kullanıyorsanız ve koleksiyonları değiştirmek istiyorsanız, pasta gibi kolay. Içinde çok büyük yorumlar.
Andrew Backer

2
@Alexandros teşekkürler, ClassGraph kontrol etmelisiniz, FastClasspathScanner üzerinde önemli ölçüde geliştirildi.
Luke Hutchison

2
@AndrewBacker ClassGraph (FastClasspathScanner'ın yeni sürümü), filtreler veya ayar işlemleri aracılığıyla Boole işlemleri için tam desteğe sahiptir. Kod örneklerine buradan bakın: github.com/classgraph/classgraph/wiki/Code-examples
Luke Hutchison

1
@Luke Hutchison Zaten ClassGraph kullanıyor. Java 10'a geçişte bana yardımcı oldu. Gerçekten kullanışlı bir kütüphane.
Alexandros


20

Derleme işlemi sırasında çalıştırılacak ve tüm açıklamalı sınıfları toplayacak ve çalışma zamanı kullanımı için dizin dosyasını oluşturacak ek açıklama işlemcisi yazmak için Java Takılabilir Ek Açıklama İşleme API'sını kullanabilirsiniz.

Bu, açıklamalı sınıf keşfi yapmanın en hızlı yoludur, çünkü çalışma zamanında sınıf yolunuzu taramanıza gerek yoktur, bu genellikle çok yavaş bir işlemdir. Ayrıca bu yaklaşım, yalnızca çalışma zamanı tarayıcıları tarafından desteklenen URLClassLoader'larla değil, herhangi bir sınıf yükleyici ile çalışır.

Yukarıdaki mekanizma ClassIndex kütüphanesinde zaten uygulanmaktadır .

Bunu kullanmak için @IndexAnnotated meta ek açıklamasıyla özel ek açıklamanıza açıklama ekleyin . Bu, derleme sırasında bir dizin dosyası oluşturur: META-INF / ek açıklamalar / com / test / Tüm özel sınıfları listeleyen YourCustomAnnotation. Dizini aşağıdaki koşullarda çalıştırarak erişebilirsiniz:

ClassIndex.getAnnotated(com.test.YourCustomAnnotation.class)

5

Cevaplamak için çok mu geç? ClassPathScanningCandidateComponentProvider veya Scannotations gibi Kitaplıklar tarafından gitmek daha iyi olduğunu söyleyebilirim

Ancak birisi classLoader ile üzerinde bazı eller denemek istedikten sonra bile, paketteki sınıflardan ek açıklamaları yazdırmak için kendi başıma bazılarını yazdım:

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;

}
}

Yapılandırma Dosyasında paket adını koyar ve bir sınıfa kaldırırsınız.


3

Spring ile AnnotationUtils sınıfını kullanarak aşağıdakileri de yazabilirsiniz. yani:

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

Daha fazla ayrıntı ve tüm farklı yöntemler için resmi dokümanları kontrol edin: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html


4
İyi fikir, ancak nullikinci parametre olarak bir değer koyarsanız (ki bu, miras hiyerarşisinin Spring'in Ek Açıklama'yı kullanan bir sınıfı tarayacağı sınıfı tanımlar), nulluygulamaya göre her zaman geri dönersiniz.
jonashackt

3

Tüm bu cevaplarda batan zapp'ın harika bir yorumu var :

new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class)

2

Classloader API'sında "numaralandırma" yöntemi yoktur, çünkü sınıf yüklemesi "isteğe bağlı" bir etkinliktir - sınıf yolunuzda genellikle yalnızca bir kısmına ihtiyaç duyulacak binlerce sınıf bulunur (rt.jar yalnız günümüzde 48 MB!).

Yani, bile olabilir tüm sınıfları numaralandırmak, bu çok zaman ve bellek tüketen olurdu.

Basit yaklaşım, ilgili sınıfları bir kurulum dosyasında listelemektir (xml veya süslemenize uygun olanı); bunu otomatik olarak yapmak istiyorsanız, kendinizi bir JAR veya bir sınıf diziniyle sınırlayın.



1

Google Reflections, Bahar'dan çok daha hızlı görünüyor. Bu farkı aşan bu özellik isteğini buldu: http://www.opensaga.org/jira/browse/OS-738

Yansımaları kullanmak için bir neden bu çünkü benim uygulama başlangıç ​​zamanı geliştirme sırasında gerçekten önemlidir. Yansımalar da benim kullanım durumum için kullanımı çok kolay gibi görünüyor (bir arabirimin tüm uygulayıcılarını bul).


1
JIRA sorununa bakarsanız, burada istikrar sorunları nedeniyle Yansımalar'dan uzaklaştıklarına dair yorumlar var.
Wim Deblauwe

1

Yansımalara alternatif arıyorsanız Panda Utilities - AnnotationsScanner'ı tavsiye etmek istiyorum . Yansımalar kitaplığı kaynak kodunu temel alan bir Guava içermez (Guava ~ 3MB, Panda Utilities ~ 200kb sahiptir).

Ayrıca geleceğe dayalı aramalara da adanmıştır. Dahil edilen kaynakları birden çok kez taramak veya hatta birinin mevcut sınıf yolunu taramasına izin veren bir API sağlamak istiyorsanız, AnnotationsScannerProcesstüm getirilenleri önbelleğe alır ClassFiles, bu yüzden gerçekten hızlıdır.

Basit AnnotationsScannerkullanım örneği :

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

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

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

1

Bahar'ın AnnotatedTypeScannersınıf denen bir şeyi vardır .
Bu sınıf dahili olarak

ClassPathScanningCandidateComponentProvider

Bu sınıf, sınıf yolu kaynaklarının gerçek taraması için koda sahiptir . Bunu, çalışma zamanında kullanılabilen sınıf meta verilerini kullanarak yapar.

Bir kişi bu sınıfı genişletebilir veya tarama için aynı sınıfı kullanabilir. Aşağıda yapıcı tanımı verilmiştir.

/**
     * 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;
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.