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
- You can scan against specified packets
- Some class or package names can be excluded
- 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