

不会吧,有人用了两年Spring, 居然不知道包扫描是怎么实现的
source link: http://www.cnblogs.com/zyndev/p/13301925.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

全栈的自我修养: 0004 Java 包扫描实现和应用(File篇)
I may not be able to change the past, but I can learn from it.
我也许不能改变过去发生的事情,但能向过去学习。
Table of Contents
如果你曾经使用过 Spring
, 那你已经配过 包扫描路径吧,那包扫描是怎么实现的呢?让我们自己写个包扫描
用途
基于 Java
的反射机制,我们很容易根据 class
去创建一个实例对象,但如果我们根本不知道某个包下有多少对象时,我们应该怎么做呢?
在使用 Spring
框架时,会根据包扫描路径来找到所有的 class
, 并将其实例化后存入容器中。
在我们的项目中也会遇到这样的场景,比如某个包为 org.example.plugins
, 这个里面放着所有的插件,为了不每次增减插件都要手动修改代码,我们可能会想到用扫描的方式去动态获知 org.example.plugins
到底有多少 class, 当然应用场景很有很多
思路
在一开始的我们为了上传文件和下载文件这种需求,请求会在程序运行的时候去获取当前项目运行的父路径是什么,比如下面的代码 使用Class类的getResource("").getPath()获取当前.class文件所在的路径
, 或者使用 File
来实现
//实例化一个File对象。参数不同时,获取的最终结果也不同, 这里可以将 path 替换为要扫描的包路劲 例如 org/example String path = ""; File directory = new File(path); //获取标准路径。该方法需要放置在try/catch块中,或声明抛出异常 directory.getCanonicalPath(); //获取绝对路径 directory.getAbsolutePath();
其中传入指定路径
Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources("org/example"); while (resources.hasMoreElements()) { URL url = resources.nextElement(); System.out.println(url.toString()); }
输出为
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
一些小功能
通过上面的代码,我们可以大概知道使用 File
遍历方式可以简单实现一部分包扫描,那我们定义个扫描器应该有的功能和特定吧
- 可以根据指定的包进行扫描
- 可以排除一些类或者包名
- 可以过滤一些包或者类
关于过滤可以使用 Java8
的 Predicate
来实现,
简要设计
/** * class 扫描器 * * @author zhangyunan */ public class ClassScanner { /** * 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) { } /** * Do scan all classes set. * * @return the set */ public Set<Class<?>> doScanAllClasses() { return null; } }
具体实现
1. 将包路径转换为文件路径
当我们要扫描一个 org.example
包时,首先将其转换为文件格式 org/example
, 来使用 File
遍历方式
String basePackage = "org.example"; // 如果最后一个字符是“.”,则去掉 if (basePackage.endsWith(".")) { basePackage = basePackage.substring(0, basePackage.lastIndexOf('.')); } // 将包名中的“.”换成系统文件夹的“/” String basePackageFilePath = basePackage.replace('.', '/');
2. 获取真实的路径
Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(basePackageFilePath); while (resources.hasMoreElements()) { URL resource = resources.nextElement(); }
这里需要关注下 resource
的类型, 如果是 File
和 Jar
则进行解析,这篇文章主要进行 File
操作
3. 识别文件,并进行递归遍历
String protocol = resource.getProtocol(); if ("file".equals(protocol)) { String filePath = URLDecoder.decode(resource.getFile(), "UTF-8"); // 扫描文件夹中的包和类 doScanPackageClassesByFile(classes, packageName, filePath, recursive); }
测试
项目结构
@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()); }); }
结果
org.example.ClassScannerTest org.example.mapper.UserMapper org.example.App org.example.ClassScanner
完整代码
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; /** * 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, recursive); } } return classes; } /** * 在文件夹中扫描包和类 */ private void doScanPackageClassesByFile(Set<Class<?>> classes, String packageName, String packagePath, boolean recursive) throws ClassNotFoundException { // 转为文件 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()) { // 如果是目录,则递归 doScanPackageClassesByFile(classes, packageName + "." + file.getName(), file.getAbsolutePath(), recursive); } 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); } } } } }
Recommend
-
108
生于高端,死于成本? GeekCar 是最早关注 EZZY 的媒体之一,当时,他们以宝马 i3+月费...
-
47
-
15
京东 - @Fcsle - 我就返回去了一个鼠标,本身说要给我把配件扣除, 结果还是发我一个全新未拆封的,然后我咨询了下 质保重新延期两年~~~,感觉按照我现在的使用强度,这个还是用不到两年, 不过两年后的 g304 缩水了
-
6
不会吧,是关卡设计的练习题最近些许游戏行业的老师们会私信询问从事关卡设计是否需要学习建筑设计。这是个尴尬的问题,至少我所知的国内建筑教育并不适合各位老师在行业上速成的期望,加上关卡设计在原则上的运用比建筑设计来得自由许多,以至于对于...
-
6
不会吧!不会吧!产品经理不会没有女朋友吧!? 产品经理个个都是人才,说话又好听,会没有女朋友?需求分析、目标画像、用户体验咋就白给了呢?程序猿都哄得好,你哄不好女朋友?
-
4
用了两年时间,想明白了运营的底层逻辑是什么,都在这篇万字长文里了产品运营话题下的优秀答主本文有1.3万字,建议先收藏再看。原谅我这次的标题党,本来标题是「运营...
-
4
用了两年的固态,掉速严重,上网卡顿,怎么才能恢复原来的速度?|固态硬盘|内存|管理器_网易订阅 用了两年的固态,掉速严重,上网卡顿,怎么才能恢复原来的速度?...
-
4
在进行设计时,运用一些小技巧,有时会起到事半功倍的效果。本文作者总结了一些日常工作中经常会用到的figma小技巧,希望能给你带来帮助。
-
4
哈喽,伙计们!最近做了一些Linux应用开发方面的东西,感觉现在有点混乱,所以想将每个知识点模块化,并且能够搭建自己的API库,方便以后能够直接使用!今天主要来讨论一下我们最常用的打印字符串,实现调试打印和颜色打印。
-
3
不会吧?到现在还有人不了解买家秀红人视频? ...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK