Sınıf yolunda bir Java paketindeki tüm sınıfları nasıl okuyabilirim?


95

Java paketinde bulunan sınıfları okumam gerekiyor. Bu sınıflar sınıf yolunda. Bu görevi doğrudan bir Java programından yapmam gerekiyor. Yapmanın basit bir yolunu biliyor musun?

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

7
Basitçe söylemek gerekirse, hayır, bunu kolayca yapamazsınız. Bazı durumlarda işe yarayan son derece uzun bazı hileler var, ancak kesinlikle farklı bir tasarım öneriyorum.
skaffman


Çözüm, Weld projesinde bulunabilir.
Ondra Žižka

Cevap için bu bağlantıya bakın: stackoverflow.com/questions/176527/…
Karthik E

Yanıtlar:


49

Sınıf yolunuzda Spring varsa , aşağıdakiler yapacaktır.

XmlRootElement ile açıklama eklenen bir paketteki tüm sınıfları bulun:

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;
}

Kod parçacığı için teşekkürler. :) Bir aday listesinde Sınıf tekrarından kaçınmak istiyorsanız, koleksiyon türünü Listeden Set'e değiştirin. Ve hasEnclosingClass () ve getEnclosingClassName () of classMetaData kullanın. Temel sınıf hakkında getEnclosingClass yöntemini kullanın
traeper

29

Burada açıklanan Yansımalar Projesini kullanabilirsiniz

Oldukça eksiksiz ve kullanımı kolaydır.

Yukarıdaki web sitesinden kısa açıklama:

Yansımalar, sınıf yolunuzu tarar, meta verileri dizine ekler, çalışma zamanında sorgulamanıza olanak tanır ve bu bilgileri projenizdeki birçok modül için kaydedip toplayabilir.

Misal:

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

Equinox'ta çalışıyor mu? Ve eğer öyleyse, eklentileri etkinleştirmeden bunu yapabilir mi?
Etiket

ekinoks için çalışır. Reflections'ın eski sürümlerinde, paket jar vfsType ekleyin. buraya
zapp

28

Bunu kullanıyorum, dosyalarla veya jar arşivleriyle çalışıyor

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

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

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

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

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

1
File.Separator referansını çıkarırsanız ve sadece '/' kullanırsanız bu işe yarar
Will Glass

1
Yol boşluklar içerdiğinde bunun jar dosyalarıyla çalışması için bir değişiklik daha gereklidir. Jar dosyası yolunun kodunu çözmelisiniz. Değiştir: jarFileName = packageURL.getFile (); to: jarFileName = URLDecoder.decode (packageURL.getFile ());
Will Glass

kavanozda yalnızca paket adını alıyorum .. sınıf adını
alamıyorum

yeni Dosya (uri), sorununuzu boşluklarla çözecektir.
Trejkaz

entryName.lastIndexOf ('.') -1 olacaktır
marstone

11

Spring, .NET Framework'te mükemmel bir sınıf yolu arama işlevi uyguladı PathMatchingResourcePatternResolver. classpath*: Önekini kullanırsanız, belirli bir hiyerarşideki sınıflar da dahil olmak üzere tüm kaynakları bulabilir ve hatta isterseniz onları filtreleyebilirsiniz. Sonra çocukları kullanabilirsiniz AbstractTypeHierarchyTraversingFilter, AnnotationTypeFilterve AssignableTypeFiltersınıf düzeyi ek açıklamalarına yapılan ya bu kaynakları filtrelemek için veya arayüzlerinin nasıl uygular.


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

Bu çözüm EJB ortamında test edildi .


6

Scannotation ve Reflections, sınıf yolu tarama yaklaşımını kullanır:

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

Diğer bir yaklaşım, derleme zamanında tüm açıklamalı sınıfları toplayacak ve çalışma zamanı kullanımı için dizin dosyasını oluşturacak açıklama işlemcisi yazmak için Java Pluggable Annotation Processing API'yi kullanmaktır. Bu mekanizma ClassIndex kitaplığında uygulanır:

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

4

Belirli bir paketteki tüm sınıfları listelemek için en sağlam mekanizma şu anda ClassGraph'tır , çünkü yeni JPMS modül sistemi dahil olmak üzere mümkün olan en geniş sınıf yolu belirtim mekanizmalarını yönetir. (Ben yazarım.)

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

Buna birkaç yıl sonra rastladım. Bu harika bir araçtır! Yansımadan çok daha hızlı çalışır ve statik başlatıcıları çağırmamasını seviyorum. Tam da sahip olduğumuz bir sorunu çözmek için ihtiyacım olan şey.
akagixxer

3

Bu işlevsellik, bildiğim kadarıyla Java yansıma API'sinde hala şüpheli bir şekilde eksik. Bunu yaparak bir paket nesnesi elde edebilirsiniz:

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

Ancak muhtemelen fark ettiğiniz gibi, bu paketteki sınıfları listelemenize izin vermez. Şu andan itibaren, biraz daha dosya sistemi odaklı bir yaklaşım benimsemelisiniz.

Bu yazıda bazı örnek uygulamalar buldum

Sınıflarınız JAR dosyalarına gömüldüğünde bu yöntemlerin işe yarayacağından% 100 emin değilim, ancak umarım bunlardan biri sizin için yapar.

@Skaffman'a katılıyorum ... Bunu yapmanın başka bir yolu varsa, onun yerine bunu yapmanızı öneririm.


4
Şüpheli değil, sadece bu şekilde çalışmıyor. Sınıflar paketlere "ait" değildir, onlara referansları vardır. İlişkilendirme diğer yönü göstermiyor.
skaffman

1
@skaffman Çok ilginç bir nokta. Asla böyle düşünmedim. Öyleyse, bu düşünce yolunda ilerlediğimiz sürece, dernek neden iki yönlü değil (bu artık benim merakım için daha fazla)?
Brent Kod Yazıyor

2

eXtcos umut verici görünüyor. Aşağıdakileri sağlayan tüm sınıfları bulmak istediğinizi düşünün:

  1. "Bileşen" sınıfından genişletin ve bunları saklayın
  2. "MyComponent" ile açıklamalı ve
  3. "Ortak" pakettedir.

EXtcos ile bu,

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

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

2
  1. Bill Burke ( sınıf tarama hakkında güzel bir makale) yazdı ve ardından Scannotation'ı yazdı .

  2. Hazırda bekletme zaten bunu yazdı:

    • org.hibernate.ejb.packaging.Scanner
    • org.hibernate.ejb.packaging.NativeScanner
  3. CDI bunu çözebilir, ancak bilmiyorum - henüz tam olarak araştırmadım

.

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

Ayrıca ek açıklamalar için:

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

Makalenin bağlantısı kesildi.
user2418306

1

Ben bunu uyguladım ve çoğu durumda işe yarıyor. Uzun olduğu için burada bir dosyaya koydum .

Buradaki fikir, çoğu durumda mevcut olan sınıf kaynak dosyasının konumunu bulmaktır (bilinen bir istisna, test ettiğim kadarıyla JVM sınıf dosyalarıdır). Kod bir dizindeyse, tüm dosyaları ve yalnızca spot sınıf dosyalarını tarayın. Kod bir JAR dosyasındaysa, tüm girişleri tarayın.

Bu yöntem yalnızca şu durumlarda kullanılabilir:

  1. Keşfetmek istediğiniz aynı pakette bulunan bir sınıfınız var, Bu sınıfa SeedClass denir. Örneğin, 'java.io'daki tüm sınıfları listelemek istiyorsanız, tohum sınıfı olabilir java.io.File.

  2. Sınıflarınız bir dizinde veya bir JAR dosyasında, kaynak dosya bilgilerine sahiptir (kaynak kod dosyası değil, yalnızca kaynak dosyası). Denediğim kadarıyla, JVM sınıfı dışında neredeyse% 100 çalışıyor (bu sınıflar JVM ile birlikte geliyor).

  3. Programınızın bu sınıfların ProtectionDomain öğesine erişim iznine sahip olması gerekir. Programınız yerel olarak yüklendiyse, herhangi bir sorun olmamalıdır.

Programı yalnızca normal kullanımım için test ettim, bu yüzden hala sorun olabilir.

Umarım bu yardımcı olur.


1
çok ilginç bir şey gibi görünüyor! onu kullanmaya çalışacağım. Benim için faydalı bulursam, kodunuzu açık kaynaklı bir projede kullanabilir miyim?

1
Ben de açık kaynak yapmaya hazırlanıyorum. Öyleyse devam edin: D
NawaMan

@NawaMan: Sonunda açık kaynak kullandın mı? Eğer öyleyse: son sürümü nerede bulabiliriz? Teşekkürler!
Joanis

1

İşte başka bir seçenek, yukarıda / aşağıda başka bir cevaba küçük bir değişiklik:

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

0

Uygulamaların yaygın olduğu zamanlarda, sınıf yolunda bir URL olabilirdi. Sınıf yükleyici bir sınıfa ihtiyaç duyduğunda, http kaynakları da dahil olmak üzere sınıf yolu üzerindeki tüm konumları arayacaktır. Sınıf yolunda URL'ler ve dizinler gibi şeylere sahip olabileceğiniz için, sınıfların kesin bir listesini almanın kolay bir yolu yoktur.

Ancak oldukça yaklaşabilirsiniz. Bahar kütüphanelerinden bazıları bunu şimdi yapıyor. Sınıf yolundaki tüm kavanozları alabilir ve dosyalar gibi açabilirsiniz. Daha sonra bu dosya listesini alabilir ve sınıflarınızı içeren bir veri yapısı oluşturabilirsiniz.


0

bağımlılık maven kullanın:

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

sonra bu kodu kullanın:

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

-2

Brent - ilişkilendirmenin bir yol olmasının nedeni, CLASSPATH'ınızın herhangi bir bileşenindeki herhangi bir sınıfın kendisini herhangi bir pakette (java / javax hariç) beyan edebilmesi gerçeğiyle ilgilidir. Bu nedenle, belirli bir "paket" içindeki TÜM sınıfların eşlenmesi yoktur çünkü kimse bilmiyor ve bilemez. Yarın bir jar dosyasını güncelleyebilir ve sınıfları kaldırabilir veya ekleyebilirsiniz. Bu, dünyanın tüm ülkelerindeki John / Jon / Johan adlı tüm insanların bir listesini almaya çalışmak gibi - hiçbirimiz her şeyi bilen değiliz, bu nedenle hiçbirimiz asla doğru cevaba sahip olamayacağız.


Güzel bir felsefi cevap, ama o zaman CDI fasulye taraması nasıl çalışıyor? Veya Hazırda Bekletme @Entities'i nasıl tarar?
Ondra Žižka
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.