Jak odczytać wszystkie klasy z pakietu Java w ścieżce klas?


95

Muszę przeczytać klasy zawarte w pakiecie Java. Te klasy znajdują się w ścieżce klas. Muszę wykonać to zadanie bezpośrednio z programu Java. Czy znasz prosty sposób?

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

7
Mówiąc prościej, nie, nie możesz tego łatwo zrobić. Istnieje kilka bardzo długich sztuczek, które działają w niektórych sytuacjach, ale zdecydowanie sugeruję inny projekt.
skaffman


Rozwiązanie można znaleźć w projekcie Weld .
Ondra Žižka

Skorzystaj z tego linku, aby uzyskać odpowiedź: stackoverflow.com/questions/176527/…
Karthik E

Odpowiedzi:


48

Jeśli masz wiosnę w swojej ścieżce klas, zrobią to poniższe czynności.

Znajdź wszystkie klasy w pakiecie, które są oznaczone adnotacją XmlRootElement:

private List<Class> findMyTypes(String basePackage) throws IOException, ClassNotFoundException
{
    ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

    List<Class> candidates = new ArrayList<Class>();
    String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                               resolveBasePackage(basePackage) + "/" + "**/*.class";
    Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
    for (Resource resource : resources) {
        if (resource.isReadable()) {
            MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
            if (isCandidate(metadataReader)) {
                candidates.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
            }
        }
    }
    return candidates;
}

private String resolveBasePackage(String basePackage) {
    return ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage));
}

private boolean isCandidate(MetadataReader metadataReader) throws ClassNotFoundException
{
    try {
        Class c = Class.forName(metadataReader.getClassMetadata().getClassName());
        if (c.getAnnotation(XmlRootElement.class) != null) {
            return true;
        }
    }
    catch(Throwable e){
    }
    return false;
}

Dziękujemy za fragment kodu. :) Jeśli chcesz uniknąć powielania klas na liście kandydatów, zmień typ kolekcji z listy na zestaw. I użyj funkcji hasEnclosingClass () i getEnclosingClassName () klasy classMetaData. Użyj metody getEnclosingClass w odniesieniu do klasy bazowej
traeper

29

Możesz użyć opisanego tutaj projektu Reflections

Jest całkiem kompletny i łatwy w użyciu.

Krótki opis z powyższej strony internetowej:

Reflections skanuje ścieżkę klas, indeksuje metadane, umożliwia tworzenie zapytań w czasie wykonywania oraz może zapisywać i zbierać te informacje dla wielu modułów w projekcie.

Przykład:

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

Czy działa w Equinox? A jeśli tak, czy może to zrobić bez aktywowania wtyczek?
Oznacz

działa na równonoc. w starszych wersjach Reflections dodaj pakiet jar vfsType. zobacz tutaj
zapp

28

Używam tego, działa z plikami lub archiwami jar

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

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

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

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

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

1
to działa, jeśli usuniesz odniesienie do File.Separator i po prostu użyjesz „/”
Will Glass,

1
Aby to działało z plikami jar, gdy ścieżka zawiera spacje, wymagana jest jeszcze jedna zmiana. Musisz zdekodować ścieżkę do pliku jar. Zmiana: jarFileName = packageURL.getFile (); to: jarFileName = URLDecoder.decode (packageURL.getFile ());
Will Glass

dostaję tylko nazwę pakietu w jar .. nie otrzymuję nazwy klasy
AutoMEta

new File (uri) naprawi twój problem ze spacjami.
Trejkaz,

entryName.lastIndexOf ('.') będzie wynosić -1
marstone

11

Spring zaimplementował doskonałą funkcję wyszukiwania ścieżek klas w PathMatchingResourcePatternResolver. Jeśli użyjesz classpath*przedrostka:, możesz znaleźć wszystkie zasoby, w tym klasy w danej hierarchii, a nawet je przefiltrować, jeśli chcesz. Następnie można użyć dzieci AbstractTypeHierarchyTraversingFilter, AnnotationTypeFilteri AssignableTypeFilterfiltrować te zasoby zarówno na poziomie klasy lub adnotacji na interfejsach ich wdrożenia.


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

To rozwiązanie zostało przetestowane w środowisku EJB .


6

Scannotation and Reflections używają metody skanowania ścieżki klas:

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

Innym podejściem jest użycie Java Pluggable Annotation Processing API do napisania procesora adnotacji, który zbierze wszystkie klasy z adnotacjami w czasie kompilacji i zbuduje plik indeksu do użycia w czasie wykonywania. Ten mechanizm jest zaimplementowany w bibliotece ClassIndex :

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

4

Najbardziej niezawodnym mechanizmem wyświetlania wszystkich klas w danym pakiecie jest obecnie ClassGraph , ponieważ obsługuje on najszerszy możliwy zestaw mechanizmów specyfikacji ścieżek klas , w tym nowy system modułów JPMS. (Jestem autorem.)

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

Natknąłem się na to kilka lat później. To świetne narzędzie! Działa znacznie szybciej niż odbicie i podoba mi się, że nie wywołuje statycznych inicjatorów. Właśnie tego potrzebowałem, aby rozwiązać problem, który mieliśmy.
akagixxer

3

O ile wiem, tej funkcji nadal podejrzanie brakuje w Java Reflection API. Możesz uzyskać obiekt pakietu, robiąc to:

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

Ale jak zapewne zauważyłeś, nie pozwoli ci to wyświetlić klas w tym pakiecie. W tej chwili musisz przyjąć podejście bardziej zorientowane na system plików.

W tym poście znalazłem kilka przykładowych implementacji

Nie jestem w 100% pewien, że te metody zadziałają, gdy twoje klasy są zakopane w plikach JAR, ale mam nadzieję, że jedna z nich zrobi to za Ciebie.

Zgadzam się z @skaffman ... jeśli masz inny sposób, polecam to zrobić.


4
To nie jest podejrzane, po prostu nie działa w ten sposób. Klasy nie „należą” do pakietów, mają do nich odniesienia. Skojarzenie nie wskazuje w innym kierunku.
skaffman

1
@skaffman Bardzo ciekawy punkt. Nigdy o tym nie myślałem w ten sposób. Tak długo, jak podążamy tym tropem myśli, dlaczego skojarzenie nie jest dwukierunkowe (teraz jest to bardziej dla mojej własnej ciekawości)?
Brent pisze kod

2

eXtcos wygląda obiecująco. Wyobraź sobie, że chcesz znaleźć wszystkie zajęcia, które:

  1. Rozszerz z klasy „Component” i zapisz je
  2. Są opatrzone adnotacjami „MyComponent” i
  3. Znajdują się w „wspólnym” pakiecie.

Z eXtcos jest to tak proste, jak

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 napisał (fajny artykuł o skanowaniu klas), a następnie napisał Scannotation .

  2. Hibernate ma już napisane:

    • org.hibernate.ejb.packaging.Scanner
    • org.hibernate.ejb.packaging.NativeScanner
  3. CDI może rozwiązać ten problem, ale nie wiem - nie zostało jeszcze w pełni zbadane

.

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

Również w przypadku adnotacji:

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

Link do artykułu jest martwy.
user2418306

1

Tak się składa, że ​​zaimplementowałem to i działa w większości przypadków. Ponieważ jest długi, umieściłem go tutaj w pliku .

Chodzi o to, aby znaleźć lokalizację pliku źródłowego klasy, która jest dostępna w większości przypadków (znanym wyjątkiem są pliki klas JVM - o ile testowałem). Jeśli kod znajduje się w katalogu, przejrzyj wszystkie pliki i tylko znajdź pliki klas. Jeśli kod znajduje się w pliku JAR, przeskanuj wszystkie wpisy.

Tej metody można użyć tylko wtedy, gdy:

  1. Masz klasę, która znajduje się w tym samym pakiecie, który chcesz odkryć. Ta klasa nazywa się SeedClass. Na przykład, jeśli chcesz wyświetlić wszystkie klasy w „java.io”, klasą źródłową może być java.io.File.

  2. Twoje klasy znajdują się w katalogu lub w pliku JAR, który zawiera informacje o pliku źródłowym (nie plik z kodem źródłowym, ale tylko plik źródłowy). O ile próbowałem, działa prawie w 100% z wyjątkiem klasy JVM (te klasy są dostarczane z JVM).

  3. Twój program musi mieć uprawnienia dostępu do ProtectionDomain tych klas. Jeśli twój program jest ładowany lokalnie, nie powinno być problemu.

Przetestowałem program tylko do mojego zwykłego użytku, więc nadal może występować problem.

Mam nadzieję, że to pomoże.


1
wygląda na to, że to bardzo ciekawa rzecz! spróbuję go użyć. Jeśli okaże się pomocny dla mnie, czy mogę użyć twojego kodu w projekcie open source?

1
Przygotowuję się również do open-source. Więc śmiało: D
NawaMan

@NawaMan: Czy w końcu to open source? Jeśli tak: gdzie możemy znaleźć ostatnią wersję? Dzięki!
Joanis,

1

Oto kolejna opcja, niewielka modyfikacja innej odpowiedzi powyżej / poniżej:

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

0

W czasach, gdy aplety były powszechne, można było mieć adres URL w ścieżce klas. Gdy classloader wymagał klasy, przeszukiwał wszystkie lokalizacje na ścieżce klas, w tym zasoby http. Ponieważ w ścieżce klas możesz mieć takie rzeczy, jak adresy URL i katalogi, nie ma łatwego sposobu na uzyskanie ostatecznej listy klas.

Możesz jednak podejść całkiem blisko. Niektóre biblioteki Spring robią to teraz. Możesz pobrać wszystkie słoiki na ścieżce klas i otworzyć je jak pliki. Następnie możesz wziąć tę listę plików i utworzyć strukturę danych zawierającą twoje klasy.


0

użyj narzędzia zależności:

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

następnie użyj tego kodu:

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

-2

Brent - powód, dla którego asocjacja jest jednym ze sposobów, ma związek z faktem, że każda klasa w dowolnym komponencie CLASSPATH może zadeklarować się w dowolnym pakiecie (z wyjątkiem java / javax). Dlatego po prostu nie ma mapowania WSZYSTKICH klas w danym „pakiecie”, ponieważ nikt nie wie ani nie może wiedzieć. Możesz jutro zaktualizować plik jar i usunąć lub dodać klasy. To tak, jakby próbować uzyskać listę wszystkich osób o imieniu John / Jon / Johan we wszystkich krajach świata - nikt z nas nie jest wszechwiedzący, więc nikt z nas nigdy nie będzie miał prawidłowej odpowiedzi.


Dobra odpowiedź filozoficzna, ale jak wtedy działa skanowanie fasoli CDI? Albo jak Hibernacja skanuje @Entities?
Ondra Žižka
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.