当前位置:   article > 正文

史上最完整扫描包下所有类(含Jar包扫描,maven子项目扫描)_mvn 如何扫描 jar

mvn 如何扫描 jar

要扫描包下的所有类,分类路径下扫描和jar包扫描两种,其中jar包扫描又分项目中引入的三方jar包,同级maven的多个子项目jar相互引用,还有jdk jar包(这里不考虑,一般没哪个项目会扫描jdk jar包里的类).

 

我先声明一个接口,用于应对不同类型的class扫描:

  1. public interface Scan {
  2. String CLASS_SUFFIX = ".class";
  3. Set<Class<?>> search(String packageName, Predicate<Class<?>> predicate);
  4. default Set<Class<?>> search(String packageName){
  5. return search(packageName,null);
  6. }
  7. }

其中 Predicate 是jdk8新增的Lambda表达式相关内容,可以筛选要扫描的类。

 

从类路径扫描指定包下的类:

  1. public class FileScanner implements Scan {
  2. private String defaultClassPath = FileScanner.class.getResource("/").getPath();
  3. public String getDefaultClassPath() {
  4. return defaultClassPath;
  5. }
  6. public void setDefaultClassPath(String defaultClassPath) {
  7. this.defaultClassPath = defaultClassPath;
  8. }
  9. public FileScanner(String defaultClassPath) {
  10. this.defaultClassPath = defaultClassPath;
  11. }
  12. public FileScanner(){}
  13. private static class ClassSearcher{
  14. private Set<Class<?>> classPaths = new HashSet<>();
  15. private Set<Class<?>> doPath(File file,String packageName, Predicate<Class<?>> predicate,boolean flag) {
  16. if (file.isDirectory()) {
  17. //文件夹我们就递归
  18. File[] files = file.listFiles();
  19. if(!flag){
  20. packageName = packageName+"."+file.getName();
  21. }
  22. for (File f1 : files) {
  23. doPath(f1,packageName,predicate,false);
  24. }
  25. } else {//标准文件
  26. //标准文件我们就判断是否是class文件
  27. if (file.getName().endsWith(CLASS_SUFFIX)) {
  28. //如果是class文件我们就放入我们的集合中。
  29. try {
  30. Class<?> clazz = Class.forName(packageName + "."+ file.getName().substring(0,file.getName().lastIndexOf(".")));
  31. if(predicate==null||predicate.test(clazz)){
  32. classPaths.add(clazz);
  33. }
  34. } catch (ClassNotFoundException e) {
  35. throw new ScannerClassException(e.getMessage(),e);
  36. }
  37. }
  38. }
  39. return classPaths;
  40. }
  41. }
  42. @Override
  43. public Set<Class<?>> search(String packageName, Predicate<Class<?>> predicate) {
  44. //先把包名转换为路径,首先得到项目的classpath
  45. String classpath = defaultClassPath;
  46. //然后把我们的包名basPack转换为路径名
  47. String basePackPath = packageName.replace(".", File.separator);
  48. String searchPath = classpath + basePackPath;
  49. return new ClassSearcher().doPath(new File(searchPath),packageName, predicate,true);
  50. }
  51. }

从jar中扫描相关类:

  1. public class JarScanner implements Scan {
  2. @Override
  3. public Set<Class<?>> search(String packageName, Predicate<Class<?>> predicate) {
  4. Set<Class<?>> classes = new HashSet<>();
  5. try {
  6. //通过当前线程得到类加载器从而得到URL的枚举
  7. Enumeration<URL> urlEnumeration = Thread.currentThread().getContextClassLoader().getResources(packageName.replace(".", "/"));
  8. while (urlEnumeration.hasMoreElements()) {
  9. URL url = urlEnumeration.nextElement();//得到的结果大概是:jar:file:/C:/Users/ibm/.m2/repository/junit/junit/4.12/junit-4.12.jar!/org/junit
  10. String protocol = url.getProtocol();//大概是jar
  11. if ("jar".equalsIgnoreCase(protocol)) {
  12. //转换为JarURLConnection
  13. JarURLConnection connection = (JarURLConnection) url.openConnection();
  14. if (connection != null) {
  15. JarFile jarFile = connection.getJarFile();
  16. if (jarFile != null) {
  17. //得到该jar文件下面的类实体
  18. Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
  19. while (jarEntryEnumeration.hasMoreElements()) {
  20. /*entry的结果大概是这样:
  21. org/
  22. org/junit/
  23. org/junit/rules/
  24. org/junit/runners/*/
  25. JarEntry entry = jarEntryEnumeration.nextElement();
  26. String jarEntryName = entry.getName();
  27. //这里我们需要过滤不是class文件和不在basePack包名下的类
  28. if (jarEntryName.contains(".class") && jarEntryName.replaceAll("/", ".").startsWith(packageName)) {
  29. String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replace("/", ".");
  30. Class cls = Class.forName(className);
  31. if(predicate == null || predicate.test(cls)){
  32. classes.add(cls);
  33. }
  34. }
  35. }
  36. }
  37. }
  38. }else if("file".equalsIgnoreCase(protocol)){
  39. //从maven子项目中扫描
  40. FileScanner fileScanner = new FileScanner();
  41. fileScanner.setDefaultClassPath(url.getPath().replace(packageName.replace(".", "/"),""));
  42. classes.addAll(fileScanner.search(packageName,predicate));
  43. }
  44. }
  45. }catch (ClassNotFoundException | IOException e){
  46. throw new ScannerClassException(e.getMessage(),e);
  47. }
  48. return classes;
  49. }
  50. }

之后,我们可以使用一个委派模式对以上两个结果进行合并,得出扫描结果:

  1. public class ScanExecutor implements Scan {
  2. private volatile static ScanExecutor instance;
  3. @Override
  4. public Set<Class<?>> search(String packageName, Predicate<Class<?>> predicate) {
  5. Scan fileSc = new FileScanner();
  6. Set<Class<?>> fileSearch = fileSc.search(packageName, predicate);
  7. Scan jarScanner = new JarScanner();
  8. Set<Class<?>> jarSearch = jarScanner.search(packageName,predicate);
  9. fileSearch.addAll(jarSearch);
  10. return fileSearch;
  11. }
  12. private ScanExecutor(){}
  13. public static ScanExecutor getInstance(){
  14. if(instance == null){
  15. synchronized (ScanExecutor.class){
  16. if(instance == null){
  17. instance = new ScanExecutor();
  18. }
  19. }
  20. }
  21. return instance;
  22. }
  23. }

封装一个工具类,方便调用:

  1. public class ClassScannerUtils {
  2. public static Set<Class<?>> searchClasses(String packageName){
  3. return searchClasses(packageName,null);
  4. }
  5. public static Set<Class<?>> searchClasses(String packageName, Predicate predicate){
  6. return ScanExecutor.getInstance().search(packageName,predicate);
  7. }
  8. }

源码地址:

https://gitee.com/luhui123/luhui_library/tree/master/commons_utils/src/main/java/com/luhui/commons/scanclass

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/240323
推荐阅读
相关标签
  

闽ICP备14008679号