Table of Contents

If you’ve ever used Spring, you’ve already configured the package scan path. How does it work? Let’s write a packet scan ourselves

use

With Java’s reflection mechanism, it’s easy to create an instance object based on class, but what if we don’t know how many objects are in a package?

When using the Spring framework, all classes are found based on the package scan path and instantiated into the container.

In our projects, we might have a package called org.example.plugins that contains all the plugins, and in order not to manually change the code every time we add or subtract plugins, It might be tempting to scan the org.example.plugins dynamically to find out how many classes there are in org.example.plugins, but there are many different scenarios

Train of thought

At the beginning, in order to upload files and download files, the request will get the parent path of the current project when the program is running. For example, the following code uses the Class getResource(“”).getPath() to get the path to the current.class File, or uses File

// instantiate a File object. The final result varies with the parameters. You can replace path with the packet strength to be scanned, for example org/example String path =""; File directory = new File(path); // Get the standard path. This method needs to be placed in a try/catch block, throw an exception or statement directory. GetCanonicalPath (); / / get absolute path directory. GetAbsolutePath ();Copy the code

The specified path is passed in

Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources("org/example");

while (resources.hasMoreElements()) {
  URL url = resources.nextElement();
  System.out.println(url.toString());
}
Copy the code

The output is

file:/Users/zhangyunan/project/spring-demo/java8-demo/target/test-classes/org/example
file:/Users/zhangyunan/project/spring-demo/java8-demo/target/classes/org/example
Copy the code

Some small features

From the above code, we can get a general idea of the simple implementation of a part of the package scan using File traversal, so let’s define the functions and specifi cations of the scanner

  1. You can scan against specified packets
  2. Some class or package names can be excluded
  3. You can filter some packages or classes

You can use the Java8 Predicate for filtering,

A brief design

/** * @author zhangyunan */ public class class scanner {/** * Instantiates a new class scanner BasePackage The base package * @param recursive whether to scan recursely * @param packagePredicate the package predicate * @param classPredicate the class predicate */ public ClassScanner(String basePackage, boolean recursive, Predicate<String> packagePredicate, Predicate<Class> classPredicate) { } /** * Do scan all classes set. * * @return the set*/ public Set<Class<? >>doScanAllClasses() {
    returnnull; }}Copy the code

The specific implementation

1. Convert the package path to the file path

When we scan an org.example package, we first convert it to the File format org/example to use File traversal

String basePackage = "org.example"; // If the last character is ". , then removeif (basePackage.endsWith(".")) {
  basePackage = basePackage.substring(0, basePackage.lastIndexOf('. ')); } // Put the "in the package name. Replace the system folder with "/" String basePackageFilePath = basepackage.replace ('. '.'/'); </pre>Copy the code

2. Obtain the actual path

Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(basePackageFilePath);

while (resources.hasMoreElements()) {
  URL resource = resources.nextElement();

}
Copy the code

This article will focus on the File and Jar types of resources

3. Identify files and perform recursive traversal

String protocol = resource.getProtocol();
if ("file".equals(protocol)) {
  String filePath = URLDecoder.decode(resource.getFile(), "UTF-8"); // Scan the folders for packages and classesdoScanPackageClassesByFile(classes, packageName, filePath, recursive);
}
Copy the code

test

The project structure

@Test
public void testGetPackageAllClasses() throws IOException, ClassNotFoundException {

  Predicate<String> packagePredicate = s -> true;

  ClassScanner scanner = new ClassScanner("org.example".true, packagePredicate, null); Set<Class<? >> packageAllClasses = scanner.doScanAllClasses(); packageAllClasses.forEach(it -> { System.out.println(it.getName()); }); }Copy the code

The results of

org.example.mapper.UserMapper
org.example.App
org.example.ClassScanner</pre>
Copy the code

The complete code

import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.net.URL; import java.net.URLDecoder; import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.Set; import java.util.function.Predicate; /** * @author zhangyunan */ public Class ClassScanner {private final String basePackage; private final boolean recursive; private final Predicate<String> packagePredicate; private final Predicate<Class> classPredicate; /** * Instantiates a new Class scanner. ** @param basePackage The base package * @param recursion * @param packagePredicate the package predicate * @param classPredicate the class predicate */ public ClassScanner(String basePackage, boolean recursive, Predicate<String> packagePredicate, Predicate<Class> classPredicate) { this.basePackage = basePackage; this.recursive = recursive; this.packagePredicate = packagePredicate; this.classPredicate = classPredicate; } /** * Do scan all classes set. * * @return the set* @throws IOException the io exception * @throws ClassNotFoundException the class not found exception */ public Set<Class<? >>doScanAllClasses() throws IOException, ClassNotFoundException { Set<Class<? >> classes = new LinkedHashSet<Class<? > > (); String packageName = basePackage; // If the last character is ". , then removeif (packageName.endsWith(".")) {
      packageName = packageName.substring(0, packageName.lastIndexOf('. ')); } // Put the "in the package name. Replace (basePackageFilePath = packageName. Replace ()'. '.'/');

    Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(basePackageFilePath);
    while (resources.hasMoreElements()) {
      URL resource = resources.nextElement();
      String protocol = resource.getProtocol();
      if ("file".equals(protocol)) {
        String filePath = URLDecoder.decode(resource.getFile(), "UTF-8"); // Scan the folders for packages and classesdoScanPackageClassesByFile(classes, packageName, filePath, recursive); }}returnclasses; } /** * scan packages and classes in folders */ private voiddoScanPackageClassesByFile(Set<Class<? >> classes, String packageName, String packagePath, Boolean recursive) throws ClassNotFoundException {// Convert to File File dir = new File(packagePath);if(! dir.exists() || ! dir.isDirectory()) {return; } final boolean fileRecursive = recursive; File[] dirFiles = dir.listfiles ((FileFilter) File -> {String filename = file.getName();if (file.isDirectory()) {
        if(! fileRecursive) {return false;
        }

        if(packagePredicate ! = null) {return packagePredicate.test(packageName + "." + filename);
        }
        return true;
      }

      return filename.endsWith(".class");
    });

    if (null == dirFiles) {
      return;
    }

    for (File file : dirFiles) {
      if(file.isdirectory ()) {// If it is a directory, it is recursivedoScanPackageClassesByFile(classes, packageName + "." + file.getName(), file.getAbsolutePath(), recursive);
      } else{// Remove fileName's.class 6-bit String className = file.getName().substring(0, file.getName().length() -6); Class<? > loadClass = Thread.currentThread().getContextClassLoader().loadClass(packageName +'. ' + className);
        if (classPredicate == null || classPredicate.test(loadClass)) {
          classes.add(loadClass);
        }
      }
    }
  }
}
Copy the code