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

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

 

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

public interface Scan {

    String CLASS_SUFFIX = ".class";

    Set> search(String packageName, Predicate> predicate);

    default Set> search(String packageName){
        return search(packageName,null);
    }

}

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

 

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

public class FileScanner implements Scan {

    private String defaultClassPath = FileScanner.class.getResource("/").getPath();

    public String getDefaultClassPath() {
        return defaultClassPath;
    }

    public void setDefaultClassPath(String defaultClassPath) {
        this.defaultClassPath = defaultClassPath;
    }

    public FileScanner(String defaultClassPath) {
        this.defaultClassPath = defaultClassPath;
    }

    public FileScanner(){}

    private static class ClassSearcher{
        private Set> classPaths = new HashSet<>();

        private Set> doPath(File file,String packageName, Predicate> predicate,boolean flag) {

            if (file.isDirectory()) {
                //文件夹我们就递归
                File[] files = file.listFiles();
                if(!flag){
                    packageName = packageName+"."+file.getName();
                }

                for (File f1 : files) {
                    doPath(f1,packageName,predicate,false);
                }
            } else {//标准文件
                //标准文件我们就判断是否是class文件
                if (file.getName().endsWith(CLASS_SUFFIX)) {
                    //如果是class文件我们就放入我们的集合中。
                    try {
                        Class clazz = Class.forName(packageName + "."+ file.getName().substring(0,file.getName().lastIndexOf(".")));
                        if(predicate==null||predicate.test(clazz)){
                            classPaths.add(clazz);
                        }
                    } catch (ClassNotFoundException e) {
                        throw new ScannerClassException(e.getMessage(),e);
                    }
                }
            }
            return classPaths;
        }
    }

    @Override
    public Set> search(String packageName, Predicate> predicate) {
        //先把包名转换为路径,首先得到项目的classpath
        String classpath = defaultClassPath;
        //然后把我们的包名basPack转换为路径名
        String basePackPath = packageName.replace(".", File.separator);
        String searchPath = classpath + basePackPath;
        return new ClassSearcher().doPath(new File(searchPath),packageName, predicate,true);
    }
}

从jar中扫描相关类:

public class JarScanner implements Scan {

    @Override
    public Set> search(String packageName, Predicate> predicate) {

        Set> classes = new HashSet<>();

        try {
            //通过当前线程得到类加载器从而得到URL的枚举
            Enumeration urlEnumeration = Thread.currentThread().getContextClassLoader().getResources(packageName.replace(".", "/"));
            while (urlEnumeration.hasMoreElements()) {
                URL url = urlEnumeration.nextElement();//得到的结果大概是:jar:file:/C:/Users/ibm/.m2/repository/junit/junit/4.12/junit-4.12.jar!/org/junit
                String protocol = url.getProtocol();//大概是jar
                if ("jar".equalsIgnoreCase(protocol)) {
                    //转换为JarURLConnection
                    JarURLConnection connection = (JarURLConnection) url.openConnection();
                    if (connection != null) {
                        JarFile jarFile = connection.getJarFile();
                        if (jarFile != null) {
                            //得到该jar文件下面的类实体
                            Enumeration jarEntryEnumeration = jarFile.entries();
                            while (jarEntryEnumeration.hasMoreElements()) {
                            /*entry的结果大概是这样:
                                    org/
                                    org/junit/
                                    org/junit/rules/
                                    org/junit/runners/*/
                                JarEntry entry = jarEntryEnumeration.nextElement();
                                String jarEntryName = entry.getName();
                                //这里我们需要过滤不是class文件和不在basePack包名下的类
                                if (jarEntryName.contains(".class") && jarEntryName.replaceAll("/", ".").startsWith(packageName)) {
                                    String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replace("/", ".");
                                    Class cls = Class.forName(className);
                                    if(predicate == null || predicate.test(cls)){
                                        classes.add(cls);
                                    }
                                }
                            }
                        }
                    }
                }else if("file".equalsIgnoreCase(protocol)){
                    //从maven子项目中扫描
                    FileScanner fileScanner = new FileScanner();
                    fileScanner.setDefaultClassPath(url.getPath().replace(packageName.replace(".", "/"),""));
                    classes.addAll(fileScanner.search(packageName,predicate));
                }
            }
        }catch (ClassNotFoundException | IOException e){
            throw new ScannerClassException(e.getMessage(),e);
        }
        return classes;
    }
}

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

public class ScanExecutor implements Scan {

    private volatile static ScanExecutor instance;

    @Override
    public Set> search(String packageName, Predicate> predicate) {
        Scan fileSc = new FileScanner();
        Set> fileSearch = fileSc.search(packageName, predicate);
        Scan jarScanner = new JarScanner();
        Set> jarSearch = jarScanner.search(packageName,predicate);
        fileSearch.addAll(jarSearch);
        return fileSearch;
    }

    private ScanExecutor(){}

    public static ScanExecutor getInstance(){
        if(instance == null){
            synchronized (ScanExecutor.class){
                if(instance == null){
                    instance = new ScanExecutor();
                }
            }
        }
        return instance;
    }

}

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

public class ClassScannerUtils {

    public static Set> searchClasses(String packageName){
        return searchClasses(packageName,null);
    }

    public static Set> searchClasses(String packageName, Predicate predicate){
        return ScanExecutor.getInstance().search(packageName,predicate);
    }

}

源码地址:

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

你可能感兴趣的:(程序猿,技术宅,代码狂)