如何在运行时从文件夹或 JAR 加载类?

我正在尝试制作一个 Java 工具,它将扫描 Java 应用程序的结构并提供一些有意义的信息。要做到这一点,我需要能够扫描所有的。从项目位置(JAR/WAR 或仅仅是一个文件夹)读取类文件,并使用反射来读取它们的方法。这几乎是不可能的。

我可以找到很多基于 URLClassloader 的解决方案,它们允许我从目录/存档中加载特定的类,但是没有一个解决方案允许我在没有类名或包结构信息的情况下加载类。

编辑: 我觉得我措辞不当。我的问题并不是我不能得到所有的类文件,我可以用递归等方法做到这一点,并正确地定位它们。我的问题是为每个类文件获取一个 Class 对象。

112247 次浏览

The following code loads all classes from a JAR file. It does not need to know anything about the classes. The names of the classes are extracted from the JarEntry.

JarFile jarFile = new JarFile(pathToJar);
Enumeration<JarEntry> e = jarFile.entries();


URL[] urls = { new URL("jar:file:" + pathToJar+"!/") };
URLClassLoader cl = URLClassLoader.newInstance(urls);


while (e.hasMoreElements()) {
JarEntry je = e.nextElement();
if(je.isDirectory() || !je.getName().endsWith(".class")){
continue;
}
// -6 because of .class
String className = je.getName().substring(0,je.getName().length()-6);
className = className.replace('/', '.');
Class c = cl.loadClass(className);


}

edit:

As suggested in the comments above, javassist would also be a possibility. Initialize a ClassPool somewhere before the while loop form the code above, and instead of loading the class with the class loader, you could create a CtClass object:

ClassPool cp = ClassPool.getDefault();
...
CtClass ctClass = cp.get(className);

From the ctClass, you can get all methods, fields, nested classes, .... Take a look at the javassist api: https://jboss-javassist.github.io/javassist/html/index.html

To do this, I need to be able to scan all of the .class files from the project location (JAR/WAR or just a folder)

Scanning all of the files in a folder is simple. One option is to call File.listFiles() on the File that denotes the folder, then iterate the resulting array. To traverse trees of nested folders, use recursion.

Scanning the files of a JAR file can be done using the JarFile API ... and you don't need to recurse to traverse nested "folders".

Neither of these is particularly complicated. Just read the javadoc and start coding.

List All the classes inside jar file.

public static List getClasseNames(String jarName) {
ArrayList classes = new ArrayList();


if (debug)
System.out.println("Jar " + jarName );
try {
JarInputStream jarFile = new JarInputStream(new FileInputStream(
jarName));
JarEntry jarEntry;


while (true) {
jarEntry = jarFile.getNextJarEntry();
if (jarEntry == null) {
break;
}
if (jarEntry.getName().endsWith(".class")) {
if (debug)
System.out.println("Found "
+ jarEntry.getName().replaceAll("/", "\\."));
classes.add(jarEntry.getName().replaceAll("/", "\\."));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return classes;
}

Came here with similar requirements:

Have a developing number of service classes in some package, which implement a common interface, and want to detect them at run time.

Partial problem is finding classes inside a specific package, where the application may be loaded from a jar file or from unpacked classes in a package/folder structure.

So I put the solutions from amicngh and an anonymous together.

// Retrieve classes of a package and it's nested package from file based class repository


package esc;


import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;


public class GetClasses
{
private static boolean debug = false;
    

/**
* test function with assumed package esc.util
*/
public static void main(String... args)
{
try
{
final Class<?>[] list = getClasses("esc.util");
for (final Class<?> c : list)
{
System.out.println(c.getName());
}
}
catch (final IOException e)
{
e.printStackTrace();
}
}


/**
* Scans all classes accessible from the context class loader which belong to the given package and subpackages.
*
* @precondition Thread Class loader attracts class and jar files, exclusively
* @precondition Classes with static code sections are executed, when loaded and thus must not throw exceptions
*
* @param packageName
*            [in] The base package path, dot-separated
*
* @return The classes of package /packageName/ and nested packages
*
* @throws IOException,
*             ClassNotFoundException not applicable
*
* @author Sam Ginrich, http://www.java2s.com/example/java/reflection/recursive-method-used-to-find-all-classes-in-a-given-directory-and-sub.html
*
*/
public static Class<?>[] getClasses(String packageName) throws IOException
{
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
assert classLoader != null;
if (debug)
{
System.out.println("Class Loader class is " + classLoader.getClass().getName());
}
final String packagePath = packageName.replace('.', '/');
final Enumeration<URL> resources = classLoader.getResources(packagePath);
final List<Class<?>> classes = new ArrayList<Class<?>>();
while (resources.hasMoreElements())
{
final URL resource = resources.nextElement();
final String proto = resource.getProtocol();
if ("file".equals(proto))
{
classes.addAll(findFileClasses(new File(resource.getFile()), packageName));
}
else if ("jar".equals(proto))
{
classes.addAll(findJarClasses(resource));
}
else
{
System.err.println("Protocol " + proto + " not supported");
continue;
}
}
return classes.toArray(new Class[classes.size()]);
}


    

/**
* Linear search for classes of a package from a jar file
*
* @param packageResource
*            [in] Jar URL of the base package, i.e. file URL bested in jar URL
*
* @return The classes of package /packageResource/ and nested packages
*
* @throws -
*
* @author amicngh, Sam Ginrich@stackoverflow.com
*/
private static List<Class<?>> findJarClasses(URL packageResource)
{
final List<Class<?>> classes = new ArrayList<Class<?>>();
try
{
System.out.println("Jar URL Path is " + packageResource.getPath());
final URL fileUrl = new URL(packageResource.getPath());
final String proto = fileUrl.getProtocol();
if ("file".equals(proto))
{
final String filePath = fileUrl.getPath().substring(1); // skip leading /
final int jarTagPos = filePath.indexOf(".jar!/");
if (jarTagPos < 0)
{
System.err.println("Non-conformant jar file reference " + filePath + " !");
}
else
{
final String packagePath = filePath.substring(jarTagPos + 6);
final String jarFilename = filePath.substring(0, jarTagPos + 4);
if (debug)
{
System.out.println("Package " + packagePath);
System.out.println("Jar file " + jarFilename);
}
final String packagePrefix = packagePath + '/';
try
{
final JarInputStream jarFile = new JarInputStream(
new FileInputStream(jarFilename));
JarEntry jarEntry;


while (true)
{
jarEntry = jarFile.getNextJarEntry();
if (jarEntry == null)
{
break;
}
final String classPath = jarEntry.getName();
if (classPath.startsWith(packagePrefix) && classPath.endsWith(".class"))
{
final String className = classPath
.substring(0, classPath.length() - 6).replace('/', '.');


if (debug)
{
System.out.println("Found entry " + jarEntry.getName());
}
try
{
classes.add(Class.forName(className));
}
catch (final ClassNotFoundException x)
{
System.err.println("Cannot load class " + className);
}
}
}
jarFile.close();
}
catch (final Exception e)
{
e.printStackTrace();
}
}
}
else
{
System.err.println("Nested protocol " + proto + " not supprted!");
}
}
catch (final MalformedURLException e)
{
e.printStackTrace();
}
return classes;
}


/**
* 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
* @author http://www.java2s.com/example/java/reflection/recursive-method-used-to-find-all-classes-in-a-given-directory-and-sub.html
* @throws -
*
*/
private static List<Class<?>> findFileClasses(File directory, String packageName)
{
final List<Class<?>> classes = new ArrayList<Class<?>>();
if (!directory.exists())
{
System.err.println("Directory " + directory.getAbsolutePath() + " does not exist.");
return classes;
}
final File[] files = directory.listFiles();
if (debug)
{
System.out.println("Directory "
+ directory.getAbsolutePath()
+ " has "
+ files.length
+ " elements.");
}
for (final File file : files)
{
if (file.isDirectory())
{
assert !file.getName().contains(".");
classes.addAll(findFileClasses(file, packageName + "." + file.getName()));
}
else if (file.getName().endsWith(".class"))
{
final String className = packageName
+ '.'
+ file.getName().substring(0, file.getName().length() - 6);
try
{
classes.add(Class.forName(className));
}
catch (final ClassNotFoundException cnf)
{
System.err.println("Cannot load class " + className);
}
}
}
return classes;
}
}