Java : loop on all the classes in the classpath [d

2020-01-29 08:21发布

问题:

Is there a way to iterate over all the classes in the classpath ?

I want to make some reflective checks on some classes implementing a certain interface, but I want to do it completely dynamically, without any input on which classes to check, just browsing the classpath.

回答1:

The Reflections library helps deal with this problem. As others have stated, it isn't fully possible in all class loading situations, but if all you have are jars and files, this will do it reliably.



回答2:

You can't do this elegantly.

Basically a classloader can be asked to load a specific class name, but can't be asked for all the classes it could load. (In the case of something loading classes over the web, it may not be feasible to do so - you can't reliably ask the web server to tell you all the files under a particular directory.)

If your classpath only deals with the file system, you could painfully find all the jar files in extension directories, recurse down normal classpath directories, and look inside all explicitly specified jar files - but it'll be tricky and probably fragile too.



回答3:

I have solved this problem for a single class loader. I needed reflection to write code to inspect JUnit tests and report on ignored tests.

       /**
       * 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
       */
      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>();
          try {
              ClassLoader cld = Thread.currentThread().getContextClassLoader();
              if (cld == null) {
                  throw new ClassNotFoundException("Can't get class loader.");
              }
              String path = pckgname.replace('.', '/');
              // Ask for all resources for the path
              Enumeration<URL> resources = cld.getResources(path);
              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 directory identified capture all the .class files
          for (File directory : directories) {
              if (directory.exists()) {
                  // Get the list of the files contained in the package
                  String[] files = directory.list();
                  for (String file : files) {
                      // we are only interested in .class files
                      if (file.endsWith(".class")) {
                          // removes the .class extension
                        try
                        {
                          classes.add(Class.forName(pckgname + '.' + file.substring(0, file.length() - 6)));                      
                        }
                        catch (NoClassDefFoundError e)
                        {
                          // do nothing. this class hasn't been found by the loader, and we don't care.
                        }
                      }
                  }
              } else {
                  throw new ClassNotFoundException(pckgname + " (" + directory.getPath() + ") does not appear to be a valid package");
              }
          }
          return classes;
      }  

I based my solution on the code in this thread:



回答4:

I think you'll have to inspect it manually:

String classpath = System.getProperty("java.class.path");
String[] locations = classpath.split(System.getProperty("path.separator"));
// inspect all jar's and class files in these locations, which is a pain in the b%tt

Or use some third party library that does this (I don't know of any).



回答5:

I'm not aware of a library that does this, but there are open source projects that do this for their own purposes. For example, Spring can iterate over classes in the classpath to find ones that have a certain annotation. You could take a look at how they do it to get some ideas. I'd start with the classes in the org.springframework.context.annotation package such as ClassPathScanningCandidateComponentProvider and CommonAnnotationBeanPostProcessor.



回答6:

If you use spring-context in your project: Here a working example to enumerate all types with the desired annotation:

/**
 * Lists all types in the given package (recursive) which are annotated by the given annotation.
 * <p>
 * All types which match the criteria are returned, no further checks (interface, abstract, embedded, etc.
 * are performed.
 * <p>
 *
 * @param aAnnotation
 *        the desired annotation type
 * @param aRoot
 *        the package name where to start the search.
 * @return a list of found types
 */
private Collection<? extends Class<?>> findCandidatesByAnnotation( Class<? extends Annotation> aAnnotation,
                                                                   String aRoot )
{
    List<Class<?>> result = new ArrayList<Class<?>>();

    ClassPathScanningCandidateComponentProvider scanner =
            new ClassPathScanningCandidateComponentProvider( false )
    {
        @Override
        protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition)
        {
            return true;
        }
    };

    scanner.addIncludeFilter( new AnnotationTypeFilter( aAnnotation ) );
    Set<BeanDefinition> canditates = scanner.findCandidateComponents( aRoot );
    for ( BeanDefinition beanDefinition : canditates )
    {
        try
        {
            String classname = beanDefinition.getBeanClassName();
            Class<?> clazz = Class.forName( classname );
            result.add( clazz );
        }
        catch ( ClassNotFoundException t )
        {
            myLog.error( "Springs type scanner returns a class name whose class cannot be evaluated!!!", t );
        }
    }

    return result;
}

This code was tested against spring-context-4.1.1.RELEASE under java 8.



回答7:

you can't without a custom classloader.

think for example about a case where you load your classes from a remote server that does not even offer directory listing. how can you possibly iterate over all the classes there?

the classloader interface does not provide such a function. but if you use your own classloader (or extend an existing classloader) you could add this function. in any case, it's not pretty.