È possibile trovare tutte le classi o interfacce in un determinato pacchetto? (Guardando rapidamente ad esempio Package
, sembrerebbe di no.)
È possibile trovare tutte le classi o interfacce in un determinato pacchetto? (Guardando rapidamente ad esempio Package
, sembrerebbe di no.)
Risposte:
A causa della natura dinamica dei caricatori di classi, ciò non è possibile. I programmi di caricamento classi non sono tenuti a comunicare alla VM quali classi è in grado di fornire, invece sono solo richieste passate per le classi e devono restituire una classe o generare un'eccezione.
Tuttavia, se si scrivono i propri caricatori di classi o si esaminano i percorsi di classe e i relativi vasi, è possibile trovare queste informazioni. Questo avverrà tramite operazioni sul filesystem e non riflessioni. Potrebbero anche esserci delle librerie che possono aiutarti a farlo.
Se ci sono classi che vengono generate o distribuite in remoto, non sarai in grado di scoprire quelle classi.
Il metodo normale è invece di registrare da qualche parte le classi a cui è necessario accedere in un file o fare riferimento a loro in una classe diversa. O semplicemente usa la convenzione quando si tratta di nominare.
Addendum: la Reflections Library ti permetterà di cercare le classi nel percorso di classe corrente. Può essere usato per ottenere tutte le classi in un pacchetto:
Reflections reflections = new Reflections("my.project.prefix");
Set<Class<? extends Object>> allClasses =
reflections.getSubTypesOf(Object.class);
Probabilmente dovresti dare un'occhiata alla libreria di riflessioni open source . Con esso puoi facilmente ottenere quello che vuoi.
Innanzitutto, imposta l'indice delle riflessioni (è un po 'disordinato poiché la ricerca di tutte le classi è disabilitata per impostazione predefinita):
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"))));
Quindi è possibile eseguire una query per tutti gli oggetti in un determinato pacchetto:
Set<Class<?>> classes = reflections.getSubTypesOf(Object.class);
.addUrls(ClasspathHelper.forJavaClassPath())
posto di quanto sopra li ha risolti per me. Anche meno codice!
Google Guava 14 include una nuova classe ClassPath
con tre metodi per cercare classi di livello superiore:
getTopLevelClasses()
getTopLevelClasses(String packageName)
getTopLevelClassesRecursive(String packageName)
Vedi i ClassPath
javadocs per maggiori informazioni.
ClassPath
è taggato con @Beta
, quindi potrebbe non essere una buona idea per alcuni ...
È possibile utilizzare questo metodo 1 che utilizza il 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 Questo metodo è stato preso originariamente da http://snippets.dzone.com/posts/show/4831 , che è stato archiviato da Internet Archive, come linkato ora. Lo snippet è disponibile anche su https://dzone.com/articles/get-all-classes-within-package .
%20
, ma il new File()
costruttore lo ha trattato come un valore letterale in percentuale di due zero. L'ho risolto cambiando la dirs.add(...)
linea in questo: dirs.add(new File(resource.toURI()));
ciò significava anche che dovevo aggiungere URISyntaxException
la clausola dei tiri digetClasses
Questo esempio è per Spring 4, ma è possibile trovare lo scanner classpath anche nelle versioni precedenti.
// 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 ...
}
Nota: nella versione 14, l'API è ancora contrassegnata come @Beta , quindi fai attenzione nel codice di produzione.
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
classe in Guava è anche contrassegnata con @Beta
: "Le API contrassegnate con l'annotazione @Beta a livello di classe o metodo sono soggette a modifiche. Possono essere modificate in qualsiasi modo o addirittura rimosse in qualsiasi versione principale. Se il tuo codice è una libreria stessa (ovvero viene utilizzata sul CLASSPATH degli utenti al di fuori del tuo controllo), non devi utilizzare le API beta, a meno che non li riconfezioni ... " code.google.com/p/guava-libraries/#Important_Warnings
getAllClasses()
è possibile utilizzare il metodo.
Ciao. Ho sempre avuto dei problemi con le soluzioni sopra (e su altri siti).
Come sviluppatore, sto programmando un componente aggiuntivo per un'API. L'API impedisce l'uso di librerie esterne o strumenti di terze parti. L'installazione consiste anche in una combinazione di codice in file jar o zip e file di classe che si trovano direttamente in alcune directory. Quindi il mio codice doveva essere in grado di funzionare in qualsiasi configurazione. Dopo molte ricerche ho escogitato un metodo che funzionerà in almeno il 95% di tutte le configurazioni possibili.
Il seguente codice è sostanzialmente il metodo overkill che funzionerà sempre.
Questo codice analizza un determinato pacchetto per tutte le classi incluse in esso. Funzionerà solo per tutte le classi in corso 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;
}
Questi tre metodi ti offrono la possibilità di trovare tutte le classi in un determinato pacchetto.
Lo usi in questo modo:
getClassesForPackage("package.your.classes.are.in");
Il metodo prima ottiene la corrente ClassLoader
. Recupera quindi tutte le risorse che contengono detto pacchetto e itera questi URL
s. Quindi crea un URLConnection
e determina quale tipo di URl abbiamo. Può essere una directory ( FileURLConnection
) o una directory all'interno di un file jar o zip ( JarURLConnection
). A seconda del tipo di connessione, verranno chiamati due metodi diversi.
Per prima cosa vediamo cosa succede se è un FileURLConnection
.
Controlla innanzitutto se il file passato esiste ed è una directory. In tal caso, controlla se si tratta di un file di classe. In tal caso Class
, verrà creato e inserito un oggetto ArrayList
. Se non è un file di classe ma è una directory, semplicemente eseguiamo l'iterazione e facciamo la stessa cosa. Tutti gli altri casi / file verranno ignorati.
Se URLConnection
è un JarURLConnection
, verrà chiamato l'altro metodo di supporto privato. Questo metodo scorre su tutte le voci nell'archivio zip / jar. Se una voce è un file di classe ed è all'interno del pacchetto Class
, verrà creato e archiviato un oggetto nel file ArrayList
.
Dopo che tutte le risorse sono state analizzate (il metodo principale) restituisce il ArrayList
contenimento di tutte le classi in un determinato pacchetto, che la corrente ClassLoader
conosce.
Se il processo fallisce in qualsiasi momento ClassNotFoundException
verrà lanciato un conteggio di informazioni dettagliate sulla causa esatta.
sun.net.www.protocol.file.FileURLConnection
, che genera un avviso in fase di compilazione ("warning: sun.net.www.protocol.file.FileURLConnection è un'API proprietaria di Sun e potrebbe essere rimosso in una versione futura"). Esiste un'alternativa all'utilizzo di quella classe o è possibile sopprimere l'avviso utilizzando le annotazioni?
if ( ... instanceof JarURLConnecton) { ... } else { // Asume that the Connection is valid and points to a File }
è quello che ho fatto sul mio codice per cercare le classi annotate dell'APP
Senza usare librerie extra:
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
è null
... :(
String pack = getPackage().getName();
, devi aggiungerepack = pack.replaceAll("[.]", "/");
In generale, i caricatori di classi non consentono la scansione di tutte le classi sul percorso di classe. Ma di solito l'unico caricatore di classi utilizzato è UrlClassLoader dal quale è possibile recuperare l'elenco di directory e file jar (vedere getURLs ) e aprirli uno per uno per elencare le classi disponibili. Questo approccio, chiamato scansione del percorso di classe, è implementato in Scannotation e Reflections .
Reflections reflections = new Reflections("my.package");
Set<Class<? extends Object>> classes = reflections.getSubTypesOf(Object.class);
Un altro approccio consiste nell'utilizzare l' API Java Pluggable Processing Annotation per scrivere un processore di annotazioni che raccoglierà tutte le classi annotate al momento della compilazione e creerà il file indice per l'uso in fase di runtime. Questo meccanismo è implementato nella libreria ClassIndex :
// package-info.java
@IndexSubclasses
package my.package;
// your code
Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");
Si noti che non è necessaria alcuna installazione aggiuntiva poiché la scansione è completamente automatizzata grazie al compilatore Java che rileva automaticamente tutti i processori trovati sul percorso di classe.
Il meccanismo più affidabile per elencare tutte le classi in un determinato pacchetto è attualmente ClassGraph , poiché gestisce la più ampia gamma possibile di meccanismi di specifica del percorso di classe , incluso il nuovo sistema di moduli JPMS. (Sono l'autore.)
List<String> classNames = new ArrayList<>();
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
.enableClassInfo().scan()) {
classNames.addAll(scanResult.getAllClasses().getNames());
}
Ecco come lo faccio. Eseguo la scansione di tutte le sottocartelle (sotto-pacchetti) e non provo a caricare classi anonime:
/**
* 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;
}
Ho messo insieme un semplice progetto github che risolve questo problema:
https://github.com/ddopson/java-class-enumerator
Dovrebbe funzionare per ENTRAMBE i percorsi di classe basati su file E per i file jar.
Se esegui 'make' dopo aver verificato il progetto, questo verrà stampato:
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.
Vedi anche la mia altra risposta
Sì, usando poche API che puoi, ecco come mi piace farlo, affrontato questo problema che stavo usando il core di ibernazione e ho dovuto trovare classi che erano annotate con una certa annotazione.
Trasformali in un'annotazione personalizzata con la quale contrassegnerai le classi che desideri selezionare.
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 {
}
Quindi segna la tua classe con esso come
@EntityToBeScanned
public MyClass{
}
Crea questa classe di utilità che ha il seguente metodo
public class ClassScanner {
public static Set<Class<?>> allFoundClassesAnnotatedWithEntityToBeScanned(){
Reflections reflections = new Reflections(".*");
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(EntityToBeScanned.class);
return annotated;
}
}
Chiamare il metodo allFoundClassesAnnotatedWithEntityToBeScanned () per trovare un set di classi.
Avrai bisogno delle librerie fornite di seguito
<!-- 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>
È necessario cercare ogni voce del caricatore di classi nel percorso di classe:
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());
// ....
}
Se la voce è directory, basta cercare nella sottodirectory corretta:
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));
}
}
Se la voce è il file ed è jar, ispezionare le voci ZIP di esso:
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());
}
}
Ora, una volta che hai tutti i nomi di classe nel pacchetto, puoi provare a caricarli con reflection e analizzare se sono classi o interfacce, ecc.
Ho provato a usare la libreria Reflections, ma ho avuto dei problemi nell'usarlo, e c'erano troppi barattoli che dovrei includere solo per ottenere semplicemente le classi su un pacchetto.
Pubblicherò una soluzione che ho trovato in questa duplice domanda: come ottenere tutti i nomi delle classi in un pacchetto?
La risposta è stata scritta da sp00m ; Ho aggiunto alcune correzioni per farlo funzionare:
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;
}
}
Per usarlo basta chiamare il metodo find come sp00n menzionato in questo esempio: se necessario, ho aggiunto la creazione di istanze delle classi.
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();
//...
}
Ho appena scritto una classe util, include metodi di prova, puoi avere un controllo ~
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();
}
}
La soluzione di Aleksander Blomskøld non ha funzionato per me per i test parametrici @RunWith(Parameterized.class)
durante l'utilizzo di Maven. I test sono stati nominati correttamente e anche dove sono stati trovati ma non eseguiti:
-------------------------------------------------------
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
Un problema simile è stato segnalato qui .
Nel mio caso @Parameters
sta creando istanze di ogni classe in un pacchetto. I test hanno funzionato bene se eseguiti localmente nell'IDE. Tuttavia, durante l'esecuzione di Maven non sono state trovate classi con la soluzione di Aleksander Blomskøld.
L'ho fatto funzionare con il seguente frammento ispirato al commento di David Pärsson sulla risposta di Aleksander Blomskøld:
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);
Che dire di questo:
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;
}
È quindi possibile sovraccaricare la funzione:
public static List<Class<?>> getClassesForPackage(final Package pkg) throws IOException, URISyntaxException {
return getClassesForPackage(pkg.getName());
}
Se è necessario testarlo:
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);
}
}
Se il tuo IDE non ha un supporto per l'importazione:
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;
Funziona:
dal tuo IDE
per un file JAR
senza dipendenze esterne
Quasi tutte le risposte utilizzano Reflections
o leggono i file di classe dal file system. Se si tenta di leggere le classi dal file system, è possibile che si verifichino errori durante il pacchetto dell'applicazione come JAR o altro. Inoltre, potresti non voler utilizzare una libreria separata a tale scopo.
Ecco un altro approccio che è Java puro e non dipende dal file system.
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 non è un must . È possibile utilizzare per i loop anziché i flussi. E puoi provarlo in questo modo
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);
}
ToolProvider.getSystemJavaCompiler()
, questo codice non esegue la scansione dei pacchetti nidificati.
Se non si utilizzano caricatori di classi dinamici, è possibile cercare il percorso di classe e per ogni voce cercare la directory o il file JAR.
Vale la pena citare
Se si desidera avere un elenco di tutte le classi in alcuni pacchetti, è possibile utilizzare Reflection
il seguente modo:
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));
}
Questo creerà un elenco di classi che in seguito potrai usarle come desideri.
È molto possibile, ma senza librerie aggiuntive come Reflections
è difficile ...
È difficile perché non hai lo strumento completo per ottenere il nome della classe.
E prendo il codice della mia ClassFinder
classe:
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;
}
}
Basato sulla risposta di @ Staale , e nel tentativo di non fare affidamento su librerie di terze parti, implementerei l'approccio del file system ispezionando la posizione fisica del primo pacchetto con:
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);
Se stai semplicemente cercando di caricare un gruppo di classi correlate, Spring può aiutarti.
Spring può creare un'istanza di un elenco o di una mappa di tutte le classi che implementano una determinata interfaccia in una riga di codice. L'elenco o la mappa conterrà istanze di tutte le classi che implementano tale interfaccia.
Detto questo, in alternativa al caricamento dell'elenco di classi dal file system, implementa invece la stessa interfaccia in tutte le classi che desideri caricare, indipendentemente dal pacchetto e usa Spring per fornirti le istanze di tutte. In questo modo, puoi caricare (e istanziare) tutte le classi che desideri indipendentemente dal pacchetto in cui si trovano.
D'altra parte, se averli tutti in un pacchetto è quello che vuoi, allora semplicemente tutte le classi in quel pacchetto implementano una data interfaccia.
java semplice: 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: per semplificare le piastre di caldaia per la gestione degli errori, ecc., Sto usando qui vavr
e le lombok
librerie
altre implementazioni sono state trovate nel mio repository GitHub daggerok / java-reflection-find-annotated-classi-or-method
Non riuscivo a trovare un lavoro corto tagliato per qualcosa di così semplice. Quindi eccolo qui, l'ho fatto io stesso dopo aver fatto un casino per un po ':
Reflections reflections =
new Reflections(new ConfigurationBuilder()
.filterInputsBy(new FilterBuilder().includePackage(packagePath))
.setUrls(ClasspathHelper.forPackage(packagePath))
.setScanners(new SubTypesScanner(false)));
Set<String> typeList = reflections.getAllTypes();
Se sei nella terra di primavera puoi usare PathMatchingResourcePatternResolver
;
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath*:some/package/name/*.class");
Arrays.asList(resources).forEach(r->{
...
});