Czy potrafisz znaleźć wszystkie klasy w pakiecie za pomocą refleksji?


527

Czy można znaleźć wszystkie klasy lub interfejsy w danym pakiecie? (Szybko patrząc np Package. Wydaje się, że nie.)


2
FYI rozwiązanie Amit łączy się z działaniem, chociaż ma błąd, jeśli ścieżka klasy zawiera znak spacji (i prawdopodobnie również dla innych znaków innych niż alfanumeryczne). jeśli używasz go w jakimkolwiek kodzie produkcyjnym, zobacz mój komentarz do jego odpowiedzi w celu obejścia tego problemu.
Kip

2
Zwróć także uwagę na ten post .
barfuin

1
Zobacz pokrewną odpowiedź: stackoverflow.com/a/30149061/4102160
Cfx

1
Zwróć także uwagę na ten post .
sp00m

1
Zobacz moją odpowiedź poniżej dotyczącą ClassGraph, jest to obecnie najbardziej niezawodna metoda skanowania ścieżki klasy i ścieżki modułu.
Luke Hutchison

Odpowiedzi:


373

Ze względu na dynamiczny charakter ładowarek klasowych nie jest to możliwe. Programy ładujące klasy nie są zobowiązane do powiedzenia maszynie wirtualnej, jakie klasy może ona zapewnić, zamiast tego są to tylko przekazywane żądania klas i muszą zwrócić klasę lub zgłosić wyjątek.

Jeśli jednak napiszesz własne moduły ładujące klasy lub przyjrzysz się ścieżkom klas i słojom, możesz znaleźć te informacje. Stanie się tak za pośrednictwem operacji systemu plików, a nie refleksji. Mogą nawet istnieć biblioteki, które mogą ci w tym pomóc.

Jeśli istnieją klasy, które są generowane lub dostarczane zdalnie, nie będzie można ich odkryć.

Normalną metodą jest zamiast tego zarejestrować gdzieś klasy, do których potrzebujesz dostępu w pliku, lub odwołać się do nich w innej klasie. Lub po prostu używaj konwencji, jeśli chodzi o nazewnictwo.

Dodatek: Biblioteka Refleksji pozwoli ci wyszukać klasy w bieżącej ścieżce klas. Można go użyć, aby uzyskać wszystkie klasy w pakiecie:

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

 Set<Class<? extends Object>> allClasses = 
     reflections.getSubTypesOf(Object.class);

12
Niemożność zapytania o nazwy klas od dawna mnie wkurzała. Jasne, że jest ciężko, a wydajność może się znacznie różnić, a dla niektórych modułów ładujących klasy lista jest niezdefiniowana lub nieograniczona, ale istnieją sposoby, które można by obejść.
Mr. Shiny and New 安 宇

16
Zauważ, że to rozwiązanie nie będzie działać, ponieważ getSubTypesOf domyślnie nie zwraca podtypów Object. Zobacz rozwiązanie Aleksandra Blomskølda, jak skonfigurować SubTypeScanner.
Alex Spurling,

17
Refleksje wymagają Guawy. Guawa jest duża. Wersja 14.0.1 ma 2,1 MB.
Mike Jones,

3
Nie działało dla mnie. Mac OSX - wersja zależności od odbić 0.9.9-RC1 (maven) - JDK 1.7. Ponownie rozważ przyjętą odpowiedź. @ AleksanderBlomskøld jest odpowiedzią. !!!!!
Konstantinos Margaritis

68
Jeśli to zwróci pustą listę, zainicjuj obiekt Reflections w następujący sposób: Reflections reflections = new Reflections („your.package.here”, new SubTypesScanner (false));
João Rocha da Silva

186

Prawdopodobnie powinieneś rzucić okiem na bibliotekę Reflections typu open source . Dzięki niemu możesz łatwo osiągnąć to, co chcesz.

Najpierw skonfiguruj indeks odbić (jest trochę nieporządny, ponieważ wyszukiwanie wszystkich klas jest domyślnie wyłączone):

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

Następnie możesz zapytać o wszystkie obiekty w danym pakiecie:

Set<Class<?>> classes = reflections.getSubTypesOf(Object.class);

6
Ach, zaczynamy: code.google.com/p/reflections/issues/detail?id=122 . Obiekt jest domyślnie wykluczony, ale można go ponownie ustawić. Dzięki za wskazanie mi tej biblioteki, to świetnie!
mtrc,

1
Na tym komputerze Mac napotkałem problemy z tym kodem (związane z bibliotekami natywnymi), ale użycie .addUrls(ClasspathHelper.forJavaClassPath())zamiast powyższego rozwiązało je dla mnie. Również mniej kodu!
David Pärsson,

3
jeśli ktoś zastanawia się, najprostszym sposobem na uzyskanie pakietu domyślnego jest posiadanie przedrostka pustego ciągu -> „”.
JBA

2
Biblioteka „Reflections” ma trudną licencję: github.com/ronmamo/reflections/blob/master/COPYING.txt . Sztuka polega na tym, że licencja umożliwia bezpłatne korzystanie tylko z samej licencji. Aby naprawdę korzystać z biblioteki (nie licencji), każdy musi skontaktować się z autorem i wynegocjować warunki użytkowania.
Serge Rogatch

3
Serge, myślę, że źle zrozumiałeś WTFPL : wtfpl.net Myślę, że WTFPL oznacza, że ​​możesz robić, co chcesz, nie tylko z licencją, ale także z kodem
Richo

122

Google Guava 14 zawiera nową klasę ClassPathz trzema metodami skanowania w poszukiwaniu klas najwyższego poziomu:

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

Zobacz ClassPathjavadocs aby uzyskać więcej informacji.


działało to dla mnie, gdy Reflection nie mogło zrobić (brak wspólnego bezpośredniego przodka, brak wspólnej adnotacji)
Riccardo Cossu

1
Jak wspomniałem w komentarzu poniżej , ClassPathjest oznaczony @Beta, więc może nie być dobrym pomysłem dla niektórych ...
Christian

1
Mówienie, że to działa tam, gdzie odbicie nie działa, jest nieco dziwne, rozwiązanie niewątpliwie jest realizowane za pomocą funkcji odbicia (i modułu ładującego klasy).
Maarten Bodewes,

5
Myślę, że miał na myśli bibliotekę Reflections wspomnianą w innej odpowiedzi.
Christoph Leiter,

Działa pod Javą 11, jeśli używasz guava w wersji 28.1-jre.
gorjanz

111

Możesz użyć tej metody 1, która używa 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 Ta metoda została pierwotnie zaczerpnięta z http://snippets.dzone.com/posts/show/4831 , który został zarchiwizowany przez Internet Archive, tak jak teraz. Fragment jest również dostępny na stronie https://dzone.com/articles/get-all-classes-within-package .


6
Miałem z tym problem, jeśli moja ścieżka zawierała spacje. Klasa URL uciekała spacjami do %20, ale new File()konstruktor traktował to jako dosłowny znak procentu dwa zero. Naprawiłem to, zmieniając dirs.add(...)linię na to: dirs.add(new File(resource.toURI())); Oznaczało to również, że musiałem dodać URISyntaxExceptiondo klauzuli getClasses
wyrzutów

19
Właśnie skopiowałeś z dzone.com/articles/get-all-classes-within-package ! proszę odnieść się do źródła następnym razem
RS

19
+1, ponieważ to rozwiązanie NIE wymaga bibliotek zewnętrznych ... NIGDY, naprawdę NIGDY nie należy parować kodu losowo z bibliotekami, aby osiągnąć jedną małą rzecz taką jak ta. czy wiesz, że dodajesz potencjalną powierzchnię ataku dla atakujących? W listopadzie 2015 r. Wykryto problem Apache Commons, który prowadzi do zdalnego wykonania poleceń, ponieważ Apache Commons znajduje się w ścieżce klas aplikacji wdrożonej na Jboss / Weblogic [ foxglovesecurity.com/2015/11/06/…
sc0p

@Qix poprawnie zauważył, że ten kod nie obsługuje jar. W celu obsługi słoików i katalogów . Kod został zmieniony, jak zauważono poniżej:
użytkownik1523177

1
Dobre rozwiązanie, ale wydaje się lepiej, jeśli „Class.forName (String className)” zostanie zastąpione przez „Class.forName (String className, inicjalizacja logiczna, moduł ładujący ClassLoader)” gdzie „initialize = false;” aby nie tworzyć instancji klas.
Andrei_N

99

Wiosna

Ten przykład dotyczy Spring 4, ale skaner ścieżek klasy można znaleźć również we wcześniejszych wersjach.

// 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 Guava

Uwaga: W wersji 14 interfejs API jest nadal oznaczony jako @Beta , więc należy zachować ostrożność w kodzie produkcyjnym.

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

5
Doskonała odpowiedź. Jest tu zbyt wiele rozwiązań, które są pełne, niesprawdzone, nie działają! Ten jest fantastyczny: jest zwięzły i przetestowany (pochodzi z Guawy). Bardzo dobre! Jest to przydatne, zasługuje na więcej pozytywnych opinii.
JeanValjean

Niestety ClassPathklasa w Guava jest również oznaczona @Beta: „Interfejsy API oznaczone adnotacją @Beta na poziomie klasy lub metody mogą ulec zmianie. Można je zmodyfikować w dowolny sposób, a nawet usunąć w dowolnej ważniejszej wersji. Jeśli Twój kod jest samą biblioteką (tzn. jest używana na CLASSPATH użytkowników poza Twoją kontrolą), nie powinieneś używać beta API, chyba że je przepakujesz
Christian

@Christian Dobra uwaga, nie zauważyłem! Dziękuję Ci. Dodam kolejną odpowiedź za pomocą skanera Spring Classpath, który na pewno nie jest w wersji beta.
voho

Aby znaleźć zagnieżdżone klasy statyczne za pomocą rozwiązania guava, getAllClasses()można zastosować metodę.
v.ladynev

1
Rozwiązanie Spring jest jedynym działającym, jeśli jest uruchamiane z wykonywalnego słoika.
Łukasz

38

Witaj. Zawsze miałem problemy z powyższymi rozwiązaniami (i na innych stronach).
Jako programista programuję dodatek do interfejsu API. Interfejs API uniemożliwia korzystanie z zewnętrznych bibliotek lub narzędzi innych firm. Konfiguracja składa się również z mieszanki kodu w plikach jar lub zip oraz plików klas znajdujących się bezpośrednio w niektórych katalogach. Tak więc mój kod musiał być w stanie działać w każdej konfiguracji. Po wielu badaniach wymyśliłem metodę, która będzie działać w co najmniej 95% wszystkich możliwych konfiguracji.

Poniższy kod jest w zasadzie metodą nadmiaru, która zawsze będzie działać.

Kod:

Ten kod skanuje dany pakiet pod kątem wszystkich klas, które są w nim zawarte. Będzie działać tylko dla wszystkich klas w bieżącym 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;
}

Te trzy metody umożliwiają znalezienie wszystkich klas w danym pakiecie.
Używasz go w ten sposób:

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

Wyjaśnienie:

Metoda najpierw pobiera prąd ClassLoader. Następnie pobiera wszystkie zasoby zawierające wspomniany pakiet i iteruje je URL. Następnie tworzy a URLConnectioni określa, jaki typ UR1 mamy. Może to być katalog ( FileURLConnection) lub katalog w pliku jar lub zip ( JarURLConnection). W zależności od rodzaju połączenia będziemy wywoływać dwie różne metody.

Najpierw zobaczmy, co się stanie, jeśli jest to FileURLConnection.
Najpierw sprawdza, czy przekazany plik istnieje i jest katalogiem. W takim przypadku sprawdza, czy jest to plik klasy. Jeśli tak, Classobiekt zostanie utworzony i umieszczony w ArrayList. Jeśli nie jest to plik klasy, ale katalog, po prostu iterujemy w nim i robimy to samo. Wszystkie pozostałe przypadki / pliki zostaną zignorowane.

Jeśli URLConnectionjest to JarURLConnectioninna metoda prywatnej pomocy zostanie wywołana. Ta metoda iteruje wszystkie wpisy w archiwum zip / jar. Jeśli jeden wpis jest plikiem klasy i znajduje się w pakiecie, Classobiekt zostanie utworzony i zapisany w pliku ArrayList.

Po przeanalizowaniu wszystkich zasobów (metoda główna) zwraca ArrayListzawartość wszystkich klas w danym pakiecie, o której aktualnie ClassLoaderwie.

Jeśli proces zakończy się niepowodzeniem w dowolnym momencie, ClassNotFoundExceptionzostanie wyświetlony komunikat zawierający szczegółowe informacje na temat dokładnej przyczyny.


4
Ten przykład wydaje się wymagać importu sun.net.www.protocol.file.FileURLConnection, który generuje ostrzeżenie w czasie kompilacji („ostrzeżenie: sun.net.www.protocol.file.FileURLConnection jest zastrzeżonym API firmy Sun i może zostać usunięty w przyszłej wersji”). Czy istnieje alternatywa dla korzystania z tej klasy, czy też można ostrzec to za pomocą adnotacji?
Christian

Ta metoda nie działa w przypadku klas bootstrap, takich jak java.lang, java.util, ... Można je znaleźć, pobierając System.getProperty („sun.boot.class.path”), dzieląc za pomocą: lub; (w zależności od systemu operacyjnego), a następnie uruchomione nieznacznie zmodyfikowane wersje powyższego checkDirectory i checkJarFile.
coderforlife

1
Możesz obejść to ostrzeżenie / błąd, korzystając z connection.getClass (). GetCanonicalName (). Equals („sun.net.www.protocol.file.FileURLConnection”). Jeśli naprawdę chcesz, możesz utworzyć połączenie URL, które Twoim zdaniem POWINIEN SIĘ użyć sun.net.www.protocol.file.FileURLConnection, a także porównać nazwę klasy połączenia z nazwą utworzonej klasy. Jeśli oba są takie same, możesz potraktować je jako instancję sun.net.www.protocol.file.FileURLConnection zamiast zawodzić w przypadku zmiany nazwy klasy.
William Deans

1
@Christian Możesz uniknąć FileURLConnection robiąc coś takiego: if ( ... instanceof JarURLConnecton) { ... } else { // Asume that the Connection is valid and points to a File }To właśnie zrobiłem na moim kodzie, aby przeszukiwać klasy z komentarzami JPA
Zardoz89

15

Bez użycia dodatkowych bibliotek:

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

Kiedy uruchamiam to w JAR, upackagejest null... :(
Christian

W przypadku pakietu „com.mycompany.beans” zamień „test” na „com / mycompany / beans”
James Jithin

4
Dostaję zero podczas używania tego kodu. Wydaje się, że działa tylko wtedy, gdy twój słoik jest plikiem wykonywalnym
Alao,

jeśli otrzymałeś nazwę pakietu od String pack = getPackage().getName();, musisz dodaćpack = pack.replaceAll("[.]", "/");
user2682877

13

Generalnie programy ładujące klasy nie pozwalają na skanowanie wszystkich klas w ścieżce klas. Ale zwykle jedynym używanym modułem ładującym jest UrlClassLoader, z którego możemy pobrać listę katalogów i plików jar (patrz getURL ) i otwierać je jeden po drugim, aby wyświetlić listę dostępnych klas. To podejście, zwane skanowaniem ścieżki klasy, jest zaimplementowane w Scannotation and Reflections .

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 adnotacji w czasie kompilacji i zbuduje plik indeksu na potrzeby środowiska wykonawczego. Ten mechanizm jest zaimplementowany w bibliotece ClassIndex :

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

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

Zauważ, że nie jest wymagana dodatkowa konfiguracja, ponieważ skanowanie jest w pełni zautomatyzowane, dzięki kompilatorowi Java automatycznie wykrywającemu wszystkie procesory znalezione w ścieżce klasy.


czy to odkrywa klasy spakowane w słoiku? Wydaje mi się, że to nie działa.
asgs

którego narzędzia próbujesz użyć?
Sławek

Korzystam z biblioteki Reflections. Ale zadziałało po obejściu obejścia wspomnianego przez @Aleksander Blomskøld dla najnowszych wersji tej biblioteki.
asgs

Cześć, używam zaćmienia i nie mogę go uruchomić, ClassIndex.getPackageClasses („my.package”) zwraca pustą mapę
Juan

11

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

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

5

Oto jak to robię. Skanuję wszystkie podfoldery (paczki) i nie próbuję ładować anonimowych klas:

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

4

Przygotowałem prosty projekt github, który rozwiązuje ten problem:

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

Powinno to działać zarówno ZARÓWNO dla ścieżek klas ORAZ dla plików jar.

Jeśli uruchomisz „make” po sprawdzeniu projektu, wydrukuje to:

 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. 

Zobacz także moją inną odpowiedź


4

Tak, używając kilku interfejsów API, które możesz, oto, jak lubię to robić, zmierzyłem się z tym problemem, którego użyłem hibernacji rdzenia i musiałem znaleźć klasy, które opatrzone były adnotacjami.

Zrób z nich niestandardową adnotację, za pomocą której zaznaczysz, które klasy chcesz odebrać.

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 {

}

Następnie zaznacz swoją klasę w ten sposób

@EntityToBeScanned 
public MyClass{

}

Utwórz tę klasę narzędzia, która ma następującą metodę

public class ClassScanner {

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

}

Wywołaj metodę allFoundClassesAnnotatedWithEntityToBeScanned () , aby uzyskać znaleziony zestaw klas.

Będziesz potrzebował podanych poniżej bibliotek

<!-- 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>

3

Musisz wyszukać każdą pozycję modułu ładującego klasy na ścieżce klasy:

    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());
        // ....
    }   

Jeśli wpis jest katalogiem, po prostu wyszukaj w odpowiednim podkatalogu:

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

Jeśli wpis jest plikiem i jest słoikiem, sprawdź jego wpisy ZIP:

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

Teraz, gdy masz już wszystkie nazwy klas z pakietem, możesz spróbować załadować je z refleksją i przeanalizować, czy są to klasy, czy interfejsy itp.


Co byś wprowadził dla paczki w pliku Jar?
Kyle Bridenstine,

Ten przykład nie przejdzie przez paczki. Może to zainteresuje niektórych ... @ mr-tea Po prostu określ pakiet, którego szukasz. Umieściłem to w projekcie, określiłem pakiet testowy w tym projekcie, skompilowałem i spakowałem i wywołałem przykład z głównej metody JAR. Działa jak urok. :)
Christian

3

Próbowałem użyć biblioteki Reflections, ale miałem pewne problemy z jej użyciem, a było zbyt wiele słoików, które powinienem uwzględnić, aby po prostu uzyskać klasy z pakietu.

Opublikuję rozwiązanie, które znalazłem w tym zduplikowanym pytaniu: Jak uzyskać nazwy wszystkich klas w pakiecie?

Odpowiedź została napisana przez sp00m ; Dodałem kilka poprawek, aby działało:

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

}

Aby go użyć, po prostu wywołaj metodę find jako sp00n wspomnianą w tym przykładzie: Dodałem tworzenie instancji klas w razie potrzeby.

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();
    //...
}

3

Właśnie napisałem klasę util, zawiera metody testowe, możesz sprawdzić ~

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

3

Rozwiązanie Aleksandra Blomskølda nie działało dla mnie w przypadku sparametryzowanych testów @RunWith(Parameterized.class)podczas korzystania z Maven. Testy zostały poprawnie nazwane, a także znalezione, ale nie wykonane:

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

Podobny problem został zgłoszony tutaj .

W moim przypadku @Parametersjest tworzenie instancji każdej klasy w pakiecie. Testy działały dobrze, gdy są uruchamiane lokalnie w IDE. Jednak podczas uruchamiania Maven nie znaleziono klas z rozwiązaniem Aleksandra Blomskølda.

Sprawiłem, że zadziałało z poniższym wycinkiem, zainspirowanym komentarzem Davida Pärssona do odpowiedzi Aleksandra Blomskølda:

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

3

A co z tym:

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

Następnie możesz przeciążyć funkcję:

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

Jeśli musisz to przetestować:

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

Jeśli twoje IDE nie ma pomocnika importu:

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;

To działa:

  • z twojego IDE

  • dla pliku JAR

  • bez zewnętrznych zależności


2

Prawie wszystkie odpowiedzi wykorzystują Reflectionslub odczytują pliki klas z systemu plików. Jeśli spróbujesz odczytać klasy z systemu plików, możesz napotkać błędy podczas pakowania aplikacji jako JAR lub innej. Możesz także nie chcieć używać do tego celu oddzielnej biblioteki.

Oto inne podejście, które jest czystą Javą i nie zależy od systemu plików.

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 nie jest koniecznością . Możesz używać pętli zamiast strumieni. I możesz to przetestować w ten sposób

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

1
Nie jest to bardzo przydatne, ponieważ: trzeba użyć JDKToolProvider.getSystemJavaCompiler() , ten kod nie skanuje zagnieżdżonych pakietów.
v.ladynev

Nie mogę zmusić go do pracy z
paczką

1

Pod warunkiem, że nie używasz żadnych dynamicznych programów ładujących klasy, możesz przeszukiwać ścieżkę klasy i dla każdej pozycji przeszukiwać katalog lub plik JAR.


1

Warte wspomnienia

Jeśli chcesz mieć listę wszystkich klas w ramach jednego pakietu, możesz użyć Reflectionnastępującego sposobu:

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

Spowoduje to utworzenie listy klas, z których będziesz mógł później korzystać według własnego uznania.


1

Jest to bardzo możliwe, ale bez dodatkowych bibliotek, takich jak Reflectionsto jest trudne ...
Jest trudne, ponieważ nie masz pełnego instrumentu do uzyskania nazwy klasy.
I biorę kod mojej ClassFinderklasy:

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

0

W oparciu o odpowiedź @ Staale i próbując nie polegać na bibliotekach stron trzecich, wdrożyłbym podejście do systemu plików, sprawdzając fizyczną lokalizację pierwszego pakietu za pomocą:

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

0

Jeśli chcesz tylko załadować grupę pokrewnych klas, Spring może ci pomóc.

Spring może utworzyć instancję listy lub mapy wszystkich klas, które implementują dany interfejs w jednym wierszu kodu. Lista lub mapa będą zawierać wystąpienia wszystkich klas, które implementują ten interfejs.

To powiedziawszy, jako alternatywa dla ładowania listy klas z systemu plików, zamiast tego po prostu zaimplementuj ten sam interfejs we wszystkich klasach, które chcesz załadować, niezależnie od pakietu i użyj Springa, aby zapewnić wystąpienie wszystkich z nich. W ten sposób możesz załadować (i utworzyć) wszystkie pożądane klasy, niezależnie od tego, w jakim pakiecie się znajdują.

Z drugiej strony, jeśli chcesz mieć je wszystkie w pakiecie, to po prostu wszystkie klasy w tym pakiecie implementują dany interfejs.


0

zwykła java: 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: aby uprościć plansze do obsługi błędów itp., Używam tutaj vavri lombokbibliotek

inne implementacje można znaleźć w moim repozytorium GitHub daggerok / java-reflection-find-annotated-class-or-method


0

Nie mogłem znaleźć krótkiej pracy wyciętej na coś tak prostego. A więc oto zrobiłem to sam po tym, jak trochę się przekręciłem:

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

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

0

Jeśli jesteś w Spring Spring, możesz użyć PathMatchingResourcePatternResolver;

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

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

-4

Nie jest to możliwe, ponieważ wszystkie klasy w pakiecie mogą nie zostać załadowane, podczas gdy zawsze znasz pakiet klasy.

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.