Czy można znaleźć wszystkie klasy lub interfejsy w danym pakiecie? (Szybko patrząc np Package
. Wydaje się, że nie.)
Czy można znaleźć wszystkie klasy lub interfejsy w danym pakiecie? (Szybko patrząc np Package
. Wydaje się, że nie.)
Odpowiedzi:
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);
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);
.addUrls(ClasspathHelper.forJavaClassPath())
zamiast powyższego rozwiązało je dla mnie. Również mniej kodu!
Google Guava 14 zawiera nową klasę ClassPath
z trzema metodami skanowania w poszukiwaniu klas najwyższego poziomu:
getTopLevelClasses()
getTopLevelClasses(String packageName)
getTopLevelClassesRecursive(String packageName)
Zobacz ClassPath
javadocs aby uzyskać więcej informacji.
ClassPath
jest oznaczony @Beta
, więc może nie być dobrym pomysłem dla niektórych ...
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 .
%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ć URISyntaxException
do klauzuli getClasses
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 ...
}
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
}
}
ClassPath
klasa 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
getAllClasses()
można zastosować metodę.
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ć.
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");
Metoda najpierw pobiera prąd ClassLoader
. Następnie pobiera wszystkie zasoby zawierające wspomniany pakiet i iteruje je URL
. Następnie tworzy a URLConnection
i 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, Class
obiekt 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 URLConnection
jest to JarURLConnection
inna 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, Class
obiekt zostanie utworzony i zapisany w pliku ArrayList
.
Po przeanalizowaniu wszystkich zasobów (metoda główna) zwraca ArrayList
zawartość wszystkich klas w danym pakiecie, o której aktualnie ClassLoader
wie.
Jeśli proces zakończy się niepowodzeniem w dowolnym momencie, ClassNotFoundException
zostanie wyświetlony komunikat zawierający szczegółowe informacje na temat dokładnej przyczyny.
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?
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
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;
}
}
upackage
jest null
... :(
String pack = getPackage().getName();
, musisz dodaćpack = pack.replaceAll("[.]", "/");
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.
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());
}
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;
}
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ź
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>
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.
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();
//...
}
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();
}
}
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 @Parameters
jest 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);
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
Prawie wszystkie odpowiedzi wykorzystują Reflections
lub 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);
}
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.
Warte wspomnienia
Jeśli chcesz mieć listę wszystkich klas w ramach jednego pakietu, możesz użyć Reflection
nastę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.
Jest to bardzo możliwe, ale bez dodatkowych bibliotek, takich jak Reflections
to jest trudne ...
Jest trudne, ponieważ nie masz pełnego instrumentu do uzyskania nazwy klasy.
I biorę kod mojej ClassFinder
klasy:
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;
}
}
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);
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.
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 vavr
i lombok
bibliotek
inne implementacje można znaleźć w moim repozytorium GitHub daggerok / java-reflection-find-annotated-class-or-method
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();
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->{
...
});