【问题标题】:How to load Classes at runtime from a folder or JAR?如何在运行时从文件夹或 JAR 加载类?
【发布时间】:2012-06-16 11:34:37
【问题描述】:

我正在尝试制作一个 Java 工具来扫描 Java 应用程序的结构并提供一些有意义的信息。为此,我需要能够从项目位置(JAR/WAR 或只是一个文件夹)扫描所有 .class 文件,并使用反射来了解它们的方法。事实证明这几乎是不可能的。

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

编辑: 我觉得我表达得很糟糕。我的问题不是我无法获得所有的类文件,我可以通过递归等来做到这一点并正确定位它们。我的问题是为每个类文件获取一个类对象。

【问题讨论】:

  • 如何加载一个忽略其完全限定名的类?我认为这是不可能的。
  • 下面的答案可以让您找到类文件,但根据项目的大小,可能值得使用asmjavassist。这些将让您分析类而不将它们全部加载到内存中。

标签: java class reflection jar load


【解决方案1】:

带着类似的要求来到这里:

在某些包中拥有不断发展的服务类,它们实现了一个通用的 接口,并希望在运行时检测它们。

部分问题是在特定包中查找类,其中应用程序可能从 jar 文件或从包/文件夹结构中的解压缩类加载。

所以我把来自 amicngh 和 anonymous 的解决方案放在一起。

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

【讨论】:

    【解决方案2】:

    以下代码从 JAR 文件加载所有类。它不需要知道任何关于类的信息。类的名称是从 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);
    
    }
    

    编辑:

    正如上面 cmets 中所建议的,javassist 也是一种可能。 在上面代码的 while 循环之前的某个地方初始化一个 ClassPool,而不是使用类加载器加载类,您可以创建一个 CtClass 对象:

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

    从 ctClass 中,您可以获取所有方法、字段、嵌套类...。 看一下 javassist api: https://jboss-javassist.github.io/javassist/html/index.html

    【讨论】:

    • 很抱歉提出一个旧帖子(顺便说一句,谢谢你的回答,它对我有用)。但是,new URL("jar:file:" + pathToJar+"!/") 导致了ClassNotFoundException。对我有用的是:URL[] urls = { new URL("jar:" + pathToJar+"!/") }; 不知道为什么?希望它可以帮助别人。
    • 不错。你可能想关闭 jarFile 吗?尝试 { ... } 最后 { jarFile.close(); }
    • 链接已失效。
    • 链接失效了,求大神解决!
    • - String.valueOf(".class").length() 可能比“- 6”更具表现力
    【解决方案3】:

    为此,我需要能够扫描项目位置(JAR/WAR 或只是一个文件夹)中的所有 .class 文件

    扫描文件夹中的所有文件很简单。一种选择是在表示文件夹的File 上调用File.listFiles(),然后迭代生成的数组。要遍历嵌套文件夹的树,请使用递归。

    可以使用JarFile API 来扫描 JAR 文件的文件...而且您无需递归遍历嵌套的“文件夹”。

    这些都不是特别复杂。只需阅读 javadoc 并开始编码。

    【讨论】:

      【解决方案4】:

      列出jar文件中的所有类。

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

      【讨论】:

        猜你喜欢
        • 2014-07-30
        • 2023-03-10
        • 1970-01-01
        • 2011-09-08
        • 2011-11-26
        • 2012-10-20
        • 2020-02-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多