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

网友投稿 770 2022-11-29


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

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

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

比如添加 guava 依赖

com.google.guava

guava

28.2-jre

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

@Test

public void testGetPackageAllClasses() throws IOException, ClassNotFoundException {

ClassScanner scanner = new ClassScanner("com.google.common.cache", true, null, null);

Set> 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 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> classes)

throws IOException, ClassNotFoundException {

// 包名

String packageName = basePackage;

// 获取文件路径

String basePackageFilePath = packageName.replace('.', '/');

// 转为jar包

JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();

// 遍历jar包中的元素

Enumeration 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 packagePredicate;

private final Predicate 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 packagePredicate,

Predicate classPredicate) {

this.basePackage = basePackage;

this.recursive = recursive;

this.packagePredicate = packagePredicate;

this.classPredicate = classPredicate;

}

/**

* Do scan all classes set.

*

* @return the seMroMtbt

* @throws IOException the io exception

* @throws ClassNotFoundException the class not found exception

*/

public Set> doScanAllClasses() throws IOException, ClassNotFoundException {

Set> classes = new LinkedHashSet>();

String packageName = basePackage;

// 如果最后一个字符是“.”,则去掉

if (packageName.endsWith(".")) {

packageName = packageName.substring(0, packageName.lastIndexOf('.'));

}

// 将包名中的“.”换成系统文件夹的“/”

String basePackageFilePath = packageName.replace('.', '/');

Enumeration 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> classes)

throws IOException, ClassNotFoundException {

// 包名

String packageName = basePackage;

// 获取文件路径

String basePackageFilePath = packageName.replace('.', '/');

// 转为jar包

JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();

// 遍历jar包中的元素

Enumeration 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> 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);

}

}

}

}

}


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Javaweb resin4如何配置端口虚拟目录
下一篇:Java多线程CAS操作原理代码实例解析
相关文章

 发表评论

暂时没有评论,来抢沙发吧~