如果你曾经使用过 Spring, 那你已经配过 包扫描路径吧,那包扫描是怎么实现的呢?让我们自己写个包扫描
上篇文章中介绍了使用 File 遍历的方式去进行包扫描,这篇主要补充一下jar包的扫描方式,在我们的项目中一般都会去依赖一些其他jar 包,
比如添加 guava 依赖
|
1
2
3
4
5
|
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.2-jre</version>
</dependency>
|
我们再次运行上次的测试用例
|
1
2
3
4
5
6
7
8
|
@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 的方式去处理一下
|
1
2
3
4
5
6
7
8
|
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
|
1
2
3
4
5
6
|
// 获取包名
String jarPackageName = name.substring(0, name.lastIndexOf('/')).replace("/", ".");
// 获取 class 路径, 这样就能通过类加载进行加载了
String className = name.replace('/', '.');
className = className.substring(0, className.length() - 6);
|
完整代码
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
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 扫描方式就是完成的代码了
整合后代码
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
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 包扫描实现和应用内容请搜索快网idc以前的文章或继续浏览下面的相关文章希望大家以后多多支持快网idc!
原文链接:https://www.cnblogs.com/zyndev/p/13374811.html
相关文章
- 个人服务器网站搭建:如何选择适合自己的建站程序或框架? 2025-06-10
- 64M VPS建站:能否支持高流量网站运行? 2025-06-10
- 64M VPS建站:怎样选择合适的域名和SSL证书? 2025-06-10
- 64M VPS建站:怎样优化以提高网站加载速度? 2025-06-10
- 64M VPS建站:是否适合初学者操作和管理? 2025-06-10
- 2025-07-10 怎样使用阿里云的安全工具进行服务器漏洞扫描和修复?
- 2025-07-10 怎样使用命令行工具优化Linux云服务器的Ping性能?
- 2025-07-10 怎样使用Xshell连接华为云服务器,实现高效远程管理?
- 2025-07-10 怎样利用云服务器D盘搭建稳定、高效的网站托管环境?
- 2025-07-10 怎样使用阿里云的安全组功能来增强服务器防火墙的安全性?
快网idc优惠网
QQ交流群
-
浅谈PHP array_search 和 in_array 函数效率问题
2025-05-29 24 -
2025-05-29 108
-
2025-05-29 73
-
2025-05-29 96
-
2025-05-27 70

