详解Java 包扫描实现和应用(Jar篇)

如果你曾经使用过 Spring, 那你已经配过 包扫描路径吧,那包扫描是怎么实现的呢?让我们自己写个包扫描

上篇文章中介绍了使用 File 遍历的方式去进行包扫描,这篇主要补充一下jar包的扫描方式,在我们的项目中一般都会去依赖一些其他jar 包,

比如添加 guava 依赖

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

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

@Test
public 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);
    }
   }
  }
 }
}

到此这篇关于详解Java 包扫描实现和应用(Jar篇)的文章就介绍到这了,更多相关Java 包扫描实现和应用内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java网络编程实现的简单端口扫描器示例

    本文实例讲述了Java网络编程实现的简单端口扫描器.分享给大家供大家参考,具体如下: 在计算机网络的学习中,不由得觉得这门课的零碎知识点异常之多,同时因为学习的课本是老外的教材--自顶向下方法,因此学习起来不免觉得吃力,但是从老外的教材里更能从一定高度理解计算机网络的知识体系,也是乐在其中,同时做英语的习题感觉也很有趣味,从各方面来说可以说是获益良多,认识了很多专业词汇.节课之后,便想做一个简单的端口扫描器,用的语言是java,因为实现界面很简单,同时也有封装好的Socket类可以使用,主要思路

  • java实现一个扫描包的工具类实例代码

    前言 在很多的实际场景中,我们需要得到某个包名下面所有的类,比如我们在使用SpringMVC的时候,知道SpringMVC可以扫描指定包下的所有类,在平时的开发中,我们也有这样的场景,所以今天写一个扫描包的工具类,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 代码如下: package com.gujin.utils; import java.io.File; import java.io.FileFilter; import java.io.IOException; imp

  • Java扫描文件夹下所有文件名

    MIS内容项目系统,基于文档资源管理管理的,需要扫描一个文件夹下面的所有文件,需求的代码实现. package q.test.filescanner; import java.io.File; import java.util.ArrayList; import java.util.LinkedList; import q.test.filescanner.exception.ScanFilesException; /** * @author */ public class FolderFile

  • java微信扫描公众号二维码实现登陆功能

    本文实例为大家分享了java微信扫描公众号二维码实现登陆的具体代码,供大家参考,具体内容如下 前提条件: 1.微信公众平台为服务号, 2.服务号实现了账号绑定功能,即将open_id 与业务系统中的用户名有对应关系 具体实现原理: 1.用户访问业务系统登陆页时,调用二维码接口,获得二维码的ticketid,同时将sessionid,ticketid和二维码的seceneid保存 2.返回登陆页时,根据ticketid获得微信二维码 3.页面通过ajax发送请求,判断是否已经扫描成功. 4.公众平

  • java实现电脑端扫描二维码

    本文实例为大家分享了java实现电脑端扫描二维码的具体代码,供大家参考,具体内容如下 说明:js调去电脑摄像头拍照,然后获取图片base64位编码,再将base64为编码转为bolb,通过定时异步上传到后台,在后台对图片文件进行解码,返回解码结果到页面,然后页面重新加载结果(url) 第一种方式引入js <script type="text/javascript" src="${basePath}js/jquery-1.9.min.js"></sc

  • Java实现的生成二维码统计扫描次数并转发到某个地址功能详解

    本文实例讲述了Java实现的生成二维码统计扫描次数并转发到某个地址功能.分享给大家供大家参考,具体如下: 需求: 近几天某个项目需要用户录入个自己的网址,然后系统需要根据用户的的网址生成二维码,然后用户可以拿着它给别人扫描,访问到他录入的网址,在这个过程中.我需要知道用户的二维码被扫描的次数,也就是后面根据其可以做一些扫描排名之类的. 思路: ① 先生成二维码,csdn已经有前辈写了,那么我就直接拿过来用了. ② 将用户的id,和用户录入的网址处理之后作为http get参数封装到二维码中,然后

  • 详解Java 包扫描实现和应用(Jar篇)

    如果你曾经使用过 Spring, 那你已经配过 包扫描路径吧,那包扫描是怎么实现的呢?让我们自己写个包扫描 上篇文章中介绍了使用 File 遍历的方式去进行包扫描,这篇主要补充一下jar包的扫描方式,在我们的项目中一般都会去依赖一些其他jar 包, 比如添加 guava 依赖 <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <ve

  • 详解java代码中init method和destroy method的三种使用方式

    在java的实际开发过程中,我们可能常常需要使用到init method和destroy method,比如初始化一个对象(bean)后立即初始化(加载)一些数据,在销毁一个对象之前进行垃圾回收等等. 周末对这两个方法进行了一点学习和整理,倒也不是专门为了这两个方法,而是在巩固spring相关知识的时候提到了,然后感觉自己并不是很熟悉这个,便好好的了解一下. 根据特意的去了解后,发现实际上可以有三种方式来实现init method和destroy method. 要用这两个方法,自然先要知道这两

  • 详解java实践SPI机制及浅析源码

    1.概念 正式步入今天的核心内容之前,溪源先给大家介绍一下关于SPI机制的相关概念,最后会提供实践源代码. SPI即Service Provider Interface,属于JDK内置的一种动态的服务提供发现机制,可以理解为运行时动态加载接口的实现类.更甚至,大家可以将SPI机制与设计模式中的策略模式建立联系. SPI机制: 从上图中理解SPI机制:标准化接口+策略模式+配置文件: SPI机制核心思想:系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编

  • 详解Java注解知识点

    一.注解是什么 Java 注解用于为 Java 代码提供元数据,看完这句话也许你还是一脸懵逼,用人话说就是注解不直接影响你的代码执行,仅提供信息.接下我将从注解的定义.元注解.注解属性.自定义注解.注解解析JDK 提供的注解这几个方面再次了解注解(Annotation) 二.jdk支持的注解有哪些 2.1 三种常用的注解: @SuppressWarnings 该注解的作用是阻止编译器发出某些警告信息.它可以有以下参数: deprecation :过时的类或方法警告. unchecked:执行了未

  • 详解Java是如何通过接口来创建代理并进行http请求

    场景 现在想要做这么一个事情,公司的dubbo服务都是内网的,但是提供了一个对外的出口,通过链接就能请求到对应的dubbo服务.(具体怎么做的应该就是个网关,然后将http请求转为dubbo请求,通过泛化调用去进行调用.代码看不到.)现在为了方便测试,我需要将配置的接口,通过http请求去请求对应的链接. 分析 项目的思想其实跟mybatis-spring整合包的思想差不多,都是生成代理去执行接口方法. https://www.jb51.net/article/153378.htm 项目是个简单

  • 详解Java如何实现百万数据excel导出功能

    目录 前言 1.异步处理 1.1 使用job 1.2 使用mq 2.使用easyexcel 3.分页查询 4.多个sheet 5.计算limit的起始位置 6.文件上传到OSS 7.通过WebSocket推送通知 8.总条数可配置 9.order by商品编号 总结 前言 最近我做过一个MySQL百万级别数据的excel导出功能,已经正常上线使用了. 这个功能挺有意思的,里面需要注意的细节还真不少,现在拿出来跟大家分享一下,希望对你会有所帮助. 原始需求:用户在UI界面上点击全部导出按钮,就能导

  • 详解Java 缺失的特性扩展方法

    目录 什么是扩展方法 传统写法: 使用 Stream 写法: 在 Java 中怎么实现扩展方法 准备条件 编写扩展方法 数组扩展方法 扩展静态方法 建议 什么是扩展方法 扩展方法,就是能够向现有类型直接“添加”方法,而无需创建新的派生类型.重新编译或以其他方式修改现有类型.调用扩展方法的时候,与调用在类型中实际定义的方法相比没有明显的差异. 为什么需要扩展方法 考虑要实现这样的功能:从 Redis 取出包含多个商品ID的字符串后(每个商品ID使用英文逗号分隔),先对商品ID进行去重(并能够维持元

  • 详解Java编写并运行spark应用程序的方法

    我们首先提出这样一个简单的需求: 现在要分析某网站的访问日志信息,统计来自不同IP的用户访问的次数,从而通过Geo信息来获得来访用户所在国家地区分布状况.这里我拿我网站的日志记录行示例,如下所示: 121.205.198.92 - - [21/Feb/2014:00:00:07 +0800] "GET /archives/417.html HTTP/1.1" 200 11465 "http://shiyanjun.cn/archives/417.html/" &qu

  • 详解java 中Spring jsonp 跨域请求的实例

    详解java 中Spring jsonp 跨域请求的实例 jsonp介绍 JSONP(JSON with Padding)是JSON的一种"使用模式",可用于解决主流浏览器的跨域数据访问的问题.由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的<script> 元素是一个例外.利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSO

  • 详解Java中Checked Exception与Runtime Exception 的区别

    详解Java中Checked Exception与Runtime Exception 的区别 Java里有个很重要的特色是Exception ,也就是说允许程序产生例外状况.而在学Java 的时候,我们也只知道Exception 的写法,却未必真能了解不同种类的Exception 的区别. 首先,您应该知道的是Java 提供了两种Exception 的模式,一种是执行的时候所产生的Exception (Runtime Exception),另外一种则是受控制的Exception (Checked

随机推荐