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

2025-05-29 0 31

如果你曾经使用过 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

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 详解Java 包扫描实现和应用(Jar篇) https://www.kuaiidc.com/119352.html

相关文章

发表评论
暂无评论