<h1> 全栈的自我涵养: 0005 Java 包扫描实现和利用(Jar篇) </h1>

It's not the altitude, it's the attitude.

决定所有的不是高度而是态度。

Table of Contents

  • 依赖的 Jar
  • 思路
  • 残缺代码
  • 整合后代码

如果你已经应用过 Spring, 那你曾经配过 包扫描门路吧,那包扫描是怎么实现的呢?让咱们本人写个包扫描

上篇文章中介绍了应用 File 遍历的形式去进行包扫描,这篇次要补充一下jar包的扫描形式,在咱们的我的项目中个别都会去依赖一些其余jar 包,

比方增加 guava 依赖

<dependency>    <groupId>com.google.guava</groupId>    <artifactId>guava</artifactId>    <version>28.2-jre</version></dependency>

咱们再次运行上次的测试用例

@Testpublic void testGetPackageAllClasses() throws IOException, ClassNotFoundException {    ClassScanner scanner = new ClassScanner("com.google.common.cache", true, null, null);    Set<Class<?>> packageAllClasses = scanner.doScanAllClasses();    packageAllClasses.forEach(it -> {        System.out.println(it.getName());    });}

什么都没有输入

依赖的 Jar

基于Java 的反射机制,咱们很容易依据 class 去创立一个实例对象,但如果咱们基本不晓得某个包下有多少对象时,咱们应该怎么做呢?

在应用Spring框架时,会依据包扫描门路来找到所有的 class, 并将其实例化后存入容器中。

在咱们的我的项目中也会遇到这样的场景,比方某个包为 org.example.plugins, 这个外面放着所有的插件,为了不每次增减插件都要手动批改代码,咱们可能会想到用扫描的形式去动静获知 org.example.plugins 到底有多少 class, 当然利用场景很有很多

思路

既然晓得是采纳了 jar , 那咱们应用遍历 jar 的形式去解决一下

JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();// 遍历jar包中的元素Enumeration<JarEntry> entries = jar.entries();while (entries.hasMoreElements()) {  JarEntry entry = entries.nextElement();  String name = entry.getName();}

这里获取的name 格局为 com/google/common/cache/Cache.class 是不是和上篇的文件门路很像呀, 这里能够通过对 name 进行操作获取包名class

// 获取包名String jarPackageName = name.substring(0, name.lastIndexOf('/')).replace("/", ".");// 获取 class 门路, 这样就能通过类加载进行加载了String className = name.replace('/', '.');className = className.substring(0, className.length() - 6);

残缺代码

private void doScanPackageClassesByJar(String basePackage, URL url, Set<Class<?>> classes)    throws IOException, ClassNotFoundException {  // 包名  String packageName = basePackage;  // 获取文件门路  String basePackageFilePath = packageName.replace('.', '/');  // 转为jar包  JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();  // 遍历jar包中的元素  Enumeration<JarEntry> entries = jar.entries();  while (entries.hasMoreElements()) {    JarEntry entry = entries.nextElement();    String name = entry.getName();    // 如果门路不统一,或者是目录,则持续    if (!name.startsWith(basePackageFilePath) || entry.isDirectory()) {      continue;    }    // 判断是否递归搜寻子包    if (!recursive && name.lastIndexOf('/') != basePackageFilePath.length()) {      continue;    }    if (packagePredicate != null) {      String jarPackageName = name.substring(0, name.lastIndexOf('/')).replace("/", ".");      if (!packagePredicate.test(jarPackageName)) {        continue;      }    }    // 断定是否合乎过滤条件    String className = name.replace('/', '.');    className = className.substring(0, className.length() - 6);    // 用以后线程的类加载器加载类    Class<?> loadClass = Thread.currentThread().getContextClassLoader().loadClass(className);    if (classPredicate == null || classPredicate.test(loadClass)) {      classes.add(loadClass);    }  }}

在联合上篇中 File 扫描形式就是实现的代码了

整合后代码

package org.example;import java.io.File;import java.io.FileFilter;import java.io.IOException;import java.net.JarURLConnection;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;import java.util.jar.JarEntry;import java.util.jar.JarFile;/** * class 扫描器 * * @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 recursive        是否递归扫描   * @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 (packageName.endsWith(".")) {      packageName = packageName.substring(0, packageName.lastIndexOf('.'));    }    // 将包名中的“.”换成零碎文件夹的“/”    String 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");        // 扫描文件夹中的包和类        doScanPackageClassesByFile(classes, packageName, filePath);      } else if ("jar".equals(protocol)) {        doScanPackageClassesByJar(packageName, resource, classes);      }    }    return classes;  }  private void doScanPackageClassesByJar(String basePackage, URL url, Set<Class<?>> classes)    throws IOException, ClassNotFoundException {    // 包名    String packageName = basePackage;    // 获取文件门路    String basePackageFilePath = packageName.replace('.', '/');    // 转为jar包    JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();    // 遍历jar包中的元素    Enumeration<JarEntry> entries = jar.entries();    while (entries.hasMoreElements()) {      JarEntry entry = entries.nextElement();      String name = entry.getName();      // 如果门路不统一,或者是目录,则持续      if (!name.startsWith(basePackageFilePath) || entry.isDirectory()) {        continue;      }      // 判断是否递归搜寻子包      if (!recursive && name.lastIndexOf('/') != basePackageFilePath.length()) {        continue;      }      if (packagePredicate != null) {        String jarPackageName = name.substring(0, name.lastIndexOf('/')).replace("/", ".");        if (!packagePredicate.test(jarPackageName)) {          continue;        }      }      // 断定是否合乎过滤条件      String className = name.replace('/', '.');      className = className.substring(0, className.length() - 6);      // 用以后线程的类加载器加载类      Class<?> loadClass = Thread.currentThread().getContextClassLoader().loadClass(className);      if (classPredicate == null || classPredicate.test(loadClass)) {        classes.add(loadClass);      }    }  }  /**   * 在文件夹中扫描包和类   */  private void doScanPackageClassesByFile(Set<Class<?>> classes, String packageName, String packagePath)    throws ClassNotFoundException {    // 转为文件    File dir = new File(packagePath);    if (!dir.exists() || !dir.isDirectory()) {      return;    }    // 列出文件,进行过滤    // 自定义文件过滤规定    File[] dirFiles = dir.listFiles((FileFilter) file -> {      String filename = file.getName();      if (file.isDirectory()) {        if (!recursive) {          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()) {        // 如果是目录,则递归        doScanPackageClassesByFile(classes, packageName + "." + file.getName(), file.getAbsolutePath());      } else {        // 用以后类加载器加载 去除 fileName 的 .class 6 位        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);        }      }    }  }}