详解Java 包扫描实现和应用(Jar篇)
如果你曾经使用过Spring,那你已经配过包扫描路径吧,那包扫描是怎么实现的呢?让我们自己写个包扫描
上篇文章中介绍了使用File遍历的方式去进行包扫描,这篇主要补充一下jar包的扫描方式,在我们的项目中一般都会去依赖一些其他jar包,
比如添加guava依赖
com.google.guava guava 28.2-jre
我们再次运行上次的测试用例
@Test
publicvoidtestGetPackageAllClasses()throwsIOException,ClassNotFoundException{
ClassScannerscanner=newClassScanner("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的方式去处理一下
JarFilejar=((JarURLConnection)url.openConnection()).getJarFile(); //遍历jar包中的元素 Enumerationentries=jar.entries(); while(entries.hasMoreElements()){ JarEntryentry=entries.nextElement(); Stringname=entry.getName(); }
这里获取的name格式为com/google/common/cache/Cache.class是不是和上篇的文件路径很像呀,这里可以通过对name进行操作获取包名和class
//获取包名
StringjarPackageName=name.substring(0,name.lastIndexOf('/')).replace("/",".");
//获取class路径,这样就能通过类加载进行加载了
StringclassName=name.replace('/','.');
className=className.substring(0,className.length()-6);
完整代码
privatevoiddoScanPackageClassesByJar(StringbasePackage,URLurl,Set>classes) throwsIOException,ClassNotFoundException{ //包名 StringpackageName=basePackage; //获取文件路径 StringbasePackageFilePath=packageName.replace('.','/'); //转为jar包 JarFilejar=((JarURLConnection)url.openConnection()).getJarFile(); //遍历jar包中的元素 Enumeration entries=jar.entries(); while(entries.hasMoreElements()){ JarEntryentry=entries.nextElement(); Stringname=entry.getName(); //如果路径不一致,或者是目录,则继续 if(!name.startsWith(basePackageFilePath)||entry.isDirectory()){ continue; } //判断是否递归搜索子包 if(!recursive&&name.lastIndexOf('/')!=basePackageFilePath.length()){ continue; } if(packagePredicate!=null){ StringjarPackageName=name.substring(0,name.lastIndexOf('/')).replace("/","."); if(!packagePredicate.test(jarPackageName)){ continue; } } //判定是否符合过滤条件 StringclassName=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扫描方式就是完成的代码了
整合后代码
packageorg.example;
importjava.io.File;
importjava.io.FileFilter;
importjava.io.IOException;
importjava.net.JarURLConnection;
importjava.net.URL;
importjava.net.URLDecoder;
importjava.util.Enumeration;
importjava.util.LinkedHashSet;
importjava.util.Set;
importjava.util.function.Predicate;
importjava.util.jar.JarEntry;
importjava.util.jar.JarFile;
/**
*class扫描器
*
*@authorzhangyunan
*/
publicclassClassScanner{
privatefinalStringbasePackage;
privatefinalbooleanrecursive;
privatefinalPredicatepackagePredicate;
privatefinalPredicateclassPredicate;
/**
*InstantiatesanewClassscanner.
*
*@parambasePackagethebasepackage
*@paramrecursive是否递归扫描
*@parampackagePredicatethepackagepredicate
*@paramclassPredicatetheclasspredicate
*/
publicClassScanner(StringbasePackage,booleanrecursive,PredicatepackagePredicate,
PredicateclassPredicate){
this.basePackage=basePackage;
this.recursive=recursive;
this.packagePredicate=packagePredicate;
this.classPredicate=classPredicate;
}
/**
*Doscanallclassesset.
*
*@returntheset
*@throwsIOExceptiontheioexception
*@throwsClassNotFoundExceptiontheclassnotfoundexception
*/
publicSet>doScanAllClasses()throwsIOException,ClassNotFoundException{
Set>classes=newLinkedHashSet>();
StringpackageName=basePackage;
//如果最后一个字符是“.”,则去掉
if(packageName.endsWith(".")){
packageName=packageName.substring(0,packageName.lastIndexOf('.'));
}
//将包名中的“.”换成系统文件夹的“/”
StringbasePackageFilePath=packageName.replace('.','/');
Enumerationresources=Thread.currentThread().getContextClassLoader().getResources(basePackageFilePath);
while(resources.hasMoreElements()){
URLresource=resources.nextElement();
Stringprotocol=resource.getProtocol();
if("file".equals(protocol)){
StringfilePath=URLDecoder.decode(resource.getFile(),"UTF-8");
//扫描文件夹中的包和类
doScanPackageClassesByFile(classes,packageName,filePath);
}elseif("jar".equals(protocol)){
doScanPackageClassesByJar(packageName,resource,classes);
}
}
returnclasses;
}
privatevoiddoScanPackageClassesByJar(StringbasePackage,URLurl,Set>classes)
throwsIOException,ClassNotFoundException{
//包名
StringpackageName=basePackage;
//获取文件路径
StringbasePackageFilePath=packageName.replace('.','/');
//转为jar包
JarFilejar=((JarURLConnection)url.openConnection()).getJarFile();
//遍历jar包中的元素
Enumerationentries=jar.entries();
while(entries.hasMoreElements()){
JarEntryentry=entries.nextElement();
Stringname=entry.getName();
//如果路径不一致,或者是目录,则继续
if(!name.startsWith(basePackageFilePath)||entry.isDirectory()){
continue;
}
//判断是否递归搜索子包
if(!recursive&&name.lastIndexOf('/')!=basePackageFilePath.length()){
continue;
}
if(packagePredicate!=null){
StringjarPackageName=name.substring(0,name.lastIndexOf('/')).replace("/",".");
if(!packagePredicate.test(jarPackageName)){
continue;
}
}
//判定是否符合过滤条件
StringclassName=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);
}
}
}
/**
*在文件夹中扫描包和类
*/
privatevoiddoScanPackageClassesByFile(Set>classes,StringpackageName,StringpackagePath)
throwsClassNotFoundException{
//转为文件
Filedir=newFile(packagePath);
if(!dir.exists()||!dir.isDirectory()){
return;
}
//列出文件,进行过滤
//自定义文件过滤规则
File[]dirFiles=dir.listFiles((FileFilter)file->{
Stringfilename=file.getName();
if(file.isDirectory()){
if(!recursive){
returnfalse;
}
if(packagePredicate!=null){
returnpackagePredicate.test(packageName+"."+filename);
}
returntrue;
}
returnfilename.endsWith(".class");
});
if(null==dirFiles){
return;
}
for(Filefile:dirFiles){
if(file.isDirectory()){
//如果是目录,则递归
doScanPackageClassesByFile(classes,packageName+"."+file.getName(),file.getAbsolutePath());
}else{
//用当前类加载器加载去除fileName的.class6位
StringclassName=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包扫描实现和应用内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。