É possível encontrar todas as classes ou interfaces em um determinado pacote? (Olhando rapidamente para Package
, por exemplo , pareceria não.)
É possível encontrar todas as classes ou interfaces em um determinado pacote? (Olhando rapidamente para Package
, por exemplo , pareceria não.)
Respostas:
Devido à natureza dinâmica dos carregadores de classes, isso não é possível. Os carregadores de classes não precisam informar à VM quais classes ela pode oferecer; em vez disso, são apenas solicitações de classe recebidas e precisam retornar uma classe ou lançar uma exceção.
No entanto, se você escrever seus próprios carregadores de classe ou examinar os caminhos de classe e seus jars, é possível encontrar essas informações. Isso ocorrerá através de operações do sistema de arquivos e não de reflexão. Pode até haver bibliotecas que podem ajudá-lo a fazer isso.
Se houver classes geradas ou entregues remotamente, você não poderá descobrir essas classes.
Em vez disso, o método normal é registrar em algum lugar as classes às quais você precisa acessar em um arquivo ou referenciá-las em uma classe diferente. Ou apenas use a convenção quando se trata de nomear.
Adendo: A Biblioteca de Reflexões permitirá que você procure classes no caminho de classe atual. Pode ser usado para obter todas as classes em um pacote:
Reflections reflections = new Reflections("my.project.prefix");
Set<Class<? extends Object>> allClasses =
reflections.getSubTypesOf(Object.class);
Você provavelmente deveria dar uma olhada na biblioteca de código-fonte do Reflections . Com ele, você pode facilmente alcançar o que deseja.
Primeiro, configure o índice de reflexões (é um pouco confuso, pois a pesquisa de todas as classes está desativada por padrão):
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"))));
Em seguida, você pode consultar todos os objetos em um determinado pacote:
Set<Class<?>> classes = reflections.getSubTypesOf(Object.class);
.addUrls(ClasspathHelper.forJavaClassPath())
invés do acima os resolveu para mim. Menos código também!
O Google Guava 14 inclui uma nova classe ClassPath
com três métodos para procurar classes de nível superior:
getTopLevelClasses()
getTopLevelClasses(String packageName)
getTopLevelClassesRecursive(String packageName)
Veja os ClassPath
javadocs para mais informações.
ClassPath
é marcado com @Beta
, por isso pode não ser uma boa idéia para alguns ...
Você pode usar este método 1 que usa o 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 Esse método foi obtido originalmente em http://snippets.dzone.com/posts/show/4831 , que foi arquivado pelo Internet Archive, como vinculado agora. O snippet também está disponível em https://dzone.com/articles/get-all-classes-within-package .
%20
, mas o new File()
construtor tratou isso como um sinal de porcentagem literal dois zero. Corrigi-o alterando a dirs.add(...)
linha para o seguinte: dirs.add(new File(resource.toURI()));
Isso também significava que eu tinha que adicionar URISyntaxException
à cláusula throws degetClasses
Este exemplo é para o Spring 4, mas você pode encontrar o scanner de caminho de classe em versões anteriores.
// 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: Na versão 14, a API ainda está marcada como @Beta , portanto, tenha cuidado no código de produção.
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 no Guava também está marcada com @Beta
: "As APIs marcadas com a anotação @Beta no nível da classe ou do método estão sujeitas a alterações. Elas podem ser modificadas de qualquer forma ou mesmo removidas em qualquer release principal. Se o seu código é uma biblioteca em si (ou seja, é usada no CLASSPATH de usuários fora de seu próprio controle), você não deve usar APIs beta, a menos que as reembale
getAllClasses()
método pode ser usado.
Olá. Eu sempre tive alguns problemas com as soluções acima (e em outros sites).
Eu, como desenvolvedor, estou programando um complemento para uma API. A API impede o uso de bibliotecas externas ou ferramentas de terceiros. A instalação também consiste em uma mistura de código em arquivos jar ou zip e arquivos de classe localizados diretamente em alguns diretórios. Portanto, meu código precisava funcionar em todas as configurações. Depois de muita pesquisa, criei um método que funcionará em pelo menos 95% de todas as configurações possíveis.
O código a seguir é basicamente o método de exagero que sempre funcionará.
Esse código verifica um determinado pacote para todas as classes incluídas nele. Ele funcionará apenas para todas as classes na atual 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;
}
Esses três métodos fornecem a capacidade de encontrar todas as classes em um determinado pacote.
Você o usa assim:
getClassesForPackage("package.your.classes.are.in");
O método primeiro obtém o atual ClassLoader
. Em seguida, ele busca todos os recursos que contêm o referido pacote e itera esses URL
s. Em seguida, cria um URLConnection
e determina que tipo de URl temos. Pode ser um diretório ( FileURLConnection
) ou um diretório dentro de um arquivo jar ou zip ( JarURLConnection
). Dependendo do tipo de conexão, temos dois métodos diferentes que serão chamados.
Primeiro vamos ver o que acontece se for um FileURLConnection
.
Ele primeiro verifica se o arquivo passado existe e é um diretório. Se for esse o caso, verifica se é um arquivo de classe. Nesse caso, um Class
objeto será criado e colocado no ArrayList
. Se não é um arquivo de classe, mas é um diretório, simplesmente iteramos nele e fazemos a mesma coisa. Todos os outros casos / arquivos serão ignorados.
Se o URLConnection
é, JarURLConnection
o outro método auxiliar privado será chamado. Esse método itera sobre todas as entradas no arquivo zip / jar. Se uma entrada for um arquivo de classe e estiver dentro do pacote, um Class
objeto será criado e armazenado no ArrayList
.
Depois que todos os recursos foram analisados, o método principal retorna o ArrayList
conteúdo de todas as classes no pacote fornecido, que os atuais ClassLoader
conhecem.
Se o processo falhar a qualquer momento ClassNotFoundException
, será lançado um contendo informações detalhadas sobre a causa exata.
sun.net.www.protocol.file.FileURLConnection
, o que gera um aviso em tempo de compilação ("warning: sun.net.www.protocol.file.FileURLConnection é API proprietária da Sun e pode ser removido em uma versão futura"). Existe uma alternativa ao uso dessa classe ou o aviso pode ser suprimido usando anotações?
if ( ... instanceof JarURLConnecton) { ... } else { // Asume that the Connection is valid and points to a File }
É o que eu fiz no meu código para procurar JPA anotada aulas
Sem usar nenhuma biblioteca 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();
, precisará adicionarpack = pack.replaceAll("[.]", "/");
Em geral, os carregadores de classes não permitem a varredura em todas as classes no caminho de classe. Mas geralmente o único carregador de classes usado é o UrlClassLoader, do qual podemos recuperar a lista de diretórios e arquivos jar (consulte getURLs ) e abri-los um por um para listar as classes disponíveis. Essa abordagem, chamada de rastreamento de caminho de classe, é implementada no Scannotation and Reflections .
Reflections reflections = new Reflections("my.package");
Set<Class<? extends Object>> classes = reflections.getSubTypesOf(Object.class);
Outra abordagem é usar a API do Java Pluggable Annotation Processing para gravar o processador de anotações que coletará todas as classes anotadas em tempo de compilação e criará o arquivo de índice para uso em tempo de execução. Este mecanismo é implementado na biblioteca ClassIndex :
// package-info.java
@IndexSubclasses
package my.package;
// your code
Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");
Observe que nenhuma configuração adicional é necessária, pois a verificação é totalmente automatizada, graças ao compilador Java que descobre automaticamente todos os processadores encontrados no caminho de classe.
O mecanismo mais robusto para listar todas as classes em um determinado pacote atualmente é o ClassGraph , porque ele lida com a maior variedade possível de mecanismos de especificação de caminho de classe , incluindo o novo sistema de módulo JPMS. (Eu sou o autor.)
List<String> classNames = new ArrayList<>();
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
.enableClassInfo().scan()) {
classNames.addAll(scanResult.getAllClasses().getNames());
}
Aqui está como eu faço isso. Examino todas as subpastas (subpacotes) e não tento carregar classes anônimas:
/**
* 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;
}
Eu montei um projeto simples do github que resolve esse problema:
https://github.com/ddopson/java-class-enumerator
Ele deve funcionar para AMBOS os caminhos de classe baseados em arquivo E para arquivos jar.
Se você executar 'make' depois de verificar o projeto, ele será impresso:
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.
Veja também minha outra resposta
Sim, usando poucas APIs que você pode, aqui está como eu gosto de fazê-lo, enfrentou esse problema que eu estava usando o hibernate core e tive que encontrar classes que foram anotadas com uma certa anotação.
Crie uma anotação personalizada usando a qual você marcará quais classes deseja selecionar.
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 {
}
Em seguida, marque sua classe com ela como
@EntityToBeScanned
public MyClass{
}
Crie esta classe de utilitário que possui o seguinte método
public class ClassScanner {
public static Set<Class<?>> allFoundClassesAnnotatedWithEntityToBeScanned(){
Reflections reflections = new Reflections(".*");
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(EntityToBeScanned.class);
return annotated;
}
}
Chame o método allFoundClassesAnnotatedWithEntityToBeScanned () para obter um conjunto de classes encontrado.
Você precisará das bibliotecas fornecidas abaixo
<!-- 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>
Você precisa procurar todas as entradas do carregador de classes no caminho da 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 a entrada for diretório, basta procurar no subdiretório direito:
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 a entrada é o arquivo e é jar, inspecione as entradas ZIP dele:
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());
}
}
Agora, depois de ter todos os nomes de classe dentro do pacote, você pode tentar carregá-los com reflexão e analisar se eles são classes ou interfaces, etc.
Eu tenho tentado usar a biblioteca Reflections, mas tive alguns problemas usando-a, e havia muitos jarros que eu deveria incluir apenas para simplesmente obter as classes em um pacote.
Vou postar uma solução que encontrei nesta pergunta duplicada: Como obter todos os nomes de classes em um pacote?
A resposta foi escrita por sp00m ; Eu adicionei algumas correções para fazê-lo funcionar:
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;
}
}
Para usá-lo, basta chamar o método find conforme sp00n mencionado neste exemplo: Adicionei a criação de instâncias das classes, se necessário.
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();
//...
}
Acabei de escrever uma classe util, que inclui métodos de teste, você pode verificar ~
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();
}
}
A solução de Aleksander Blomskøld não funcionou para mim em testes parametrizados @RunWith(Parameterized.class)
ao usar o Maven. Os testes foram nomeados corretamente e também onde foram encontrados, mas não executados:
-------------------------------------------------------
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
Um problema semelhante foi relatado aqui .
No meu caso, @Parameters
está criando instâncias de cada classe em um pacote. Os testes funcionaram bem quando executados localmente no IDE. No entanto, ao executar o Maven, nenhuma classe foi encontrada com a solução de Aleksander Blomskøld.
Eu fiz funcionar com o seguinte trecho, inspirado no comentário de David Pärsson sobre a resposta de 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);
Que tal isso:
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;
}
Você pode sobrecarregar a função:
public static List<Class<?>> getClassesForPackage(final Package pkg) throws IOException, URISyntaxException {
return getClassesForPackage(pkg.getName());
}
Se você precisar testá-lo:
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 o seu IDE não tiver um auxiliar de importação:
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;
Funciona:
do seu IDE
para um arquivo JAR
sem dependências externas
Quase todas as respostas que usa Reflections
ou lê arquivos de classe do sistema de arquivos. Se você tentar ler classes do sistema de arquivos, poderá ocorrer erros ao compactar seu aplicativo como JAR ou outro. Além disso, você pode não querer usar uma biblioteca separada para esse fim.
Aqui está outra abordagem que é java puro e não depende do sistema de arquivos.
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));
}
}
O Java 8 não é obrigatório . Você pode usar loops em vez de fluxos. E você pode testá-lo assim
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);
}
Desde que você não esteja usando nenhum carregador de classe dinâmico, é possível pesquisar o caminho de classe e, para cada entrada, pesquisar o diretório ou arquivo JAR.
Vale a pena mencionar
Se você deseja ter uma lista de todas as classes em algum pacote, pode usar Reflection
da seguinte maneira:
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));
}
Isso criará uma lista de classes que mais tarde você poderá usá-las como desejar.
É muito possível, mas sem bibliotecas adicionais, como Reflections
é difícil ...
É difícil porque você não possui um instrumento completo para obter o nome da classe.
E eu pego o código da minha 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;
}
}
Com base na resposta de @ Staale e em uma tentativa de não confiar em bibliotecas de terceiros, eu implementaria a abordagem do Sistema de Arquivos inspecionando a localização física do primeiro pacote com:
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 você deseja apenas carregar um grupo de classes relacionadas, o Spring pode ajudá-lo.
O Spring pode instanciar uma lista ou mapa de todas as classes que implementam uma determinada interface em uma linha de código. A lista ou mapa conterá instâncias de todas as classes que implementam essa interface.
Dito isso, como alternativa ao carregamento da lista de classes do sistema de arquivos, basta implementar a mesma interface em todas as classes que você deseja carregar, independentemente do pacote e usar o Spring para fornecer instâncias de todas elas. Dessa forma, você pode carregar (e instanciar) todas as classes que desejar, independentemente do pacote em que elas estejam.
Por outro lado, se você deseja tê-los todos em um pacote, basta fazer com que todas as classes nesse pacote implementem uma determinada interface.
java simples: 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: para simplificar clichês para lidar com erros, etc, estou usando aqui vavr
e lombok
bibliotecas
outras implementações podem ser encontradas no meu repositório GitHub daggerok / java-reflexão-encontrar-anotado-classes-ou métodos
Não consegui encontrar um trabalho curto para algo tão simples. Então aqui está, eu mesmo fiz isso depois de me meter um pouco:
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 você estiver em Spring-land, poderá usar PathMatchingResourcePatternResolver
;
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath*:some/package/name/*.class");
Arrays.asList(resources).forEach(r->{
...
});