如果你曾經使用過 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 包掃描實現和應用內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://www.cnblogs.com/zyndev/p/13374811.html