Java读取jar文件、Java加载的资源路径、Java的ClassPatch、System.getProperty("java.class.path")、springboot自动装配时扫描所有依赖jar包中的spring.factories的配置类底层原理

Java 读取JAR文件信息-PathMatchingResourcePatternResolver 解析classpath*:

Java 读取JAR文件信息

JAR 文件格式以流行的 ZIP 文件格式为基础。与 ZIP 文件不同的是,JAR 文件不仅用于压缩和发布,而且还用于部署和封装库、组件和插件程序,并可被像编译器和 JVM 这样的工具直接使用。在 JAR 中包含特殊的文件,如 manifests 和部署描述符,用来指示工具如何处理特定的 JAR-百度百科
参考资料1
参考资料2
Java遍历包中所有类方法注解-很详细

为什么想到读取JAR文件的信息

查看spring 资源处理,查找多个资源classpath*,会去寻找jar包中的内容,因此会出现读取jar包中的文件或者读取文件夹中的文件等等形式。主要的类PathMatchingResourcePatternResolver

通过阅读源码和查询JavaAPI资源有两种读取的方式

  1. 通过加载文件的形式读取 JarFile jarFile = new JarFile(jarAddress);
    你可能注意到当文件不在class path中时,JarFile类对于从JAR中读取文件文件是很有用的。当你想指定目标JAR文件时,JarFile类对于从JAR中读取文件同样也很有用。

    @Test
        public void readJarFile() throws IOException{
            String jarAddress = "F:/project/...../WEB-INF/lib/activation-1.1.jar";
            JarFile jarFile = new JarFile(jarAddress);
            Enumeration<JarEntry> entries  = jarFile.entries();//遍历整个jar文件
            while (entries.hasMoreElements()) {
                JarEntry jarEntry = entries.nextElement();
                if(!jarEntry.isDirectory()){ //是否为一个文件夹
                    // javax/activation/DataSource.class
                    System.out.println(jarEntry.getName());//获取文件的路径信息
                    jarFile.getInputStream(jarEntry);//获取文件流信息
                    System.out.println(IOUtils.toString(jarFile.getInputStream(jarEntry)));
                }
            }
        }
    
  2. 通过URL的方式.getConnection获取

    @Test
        public void JarURLConnection() throws IOException{
            //这里可以是JAR包中的子文件夹或者子文件的信息
            URL url = new URL("jar:file:F:/project/...../WEB-INF/lib/activation-1.1.jar!/");
            JarURLConnection jarConnection = (JarURLConnection)url.openConnection();
            System.out.println("文件路径:"+jarConnection.getURL().getFile());
            System.out.println("入口文件:"+jarConnection.getEntryName());
            System.out.println("Manifest :"+jarConnection.getManifest ().getEntries().toString());
            JarFile jarFile = jarConnection.getJarFile(); //获取Jar文件夹
            Enumeration<JarEntry> entries  = jarFile.entries();
           while (entries.hasMoreElements()) {
               JarEntry jarEntry = entries.nextElement();
               if(!jarEntry.isDirectory()){
                   // javax/activation/DataSource.class
                   System.out.println(jarEntry.getName());
               }
           }
        }
    

如果JAR在ClassPath下面获取指定路径下的文件信息

URL url = ClassLoader.getSystemResource(name); //单个
 Enumeration<URL> resources ClassLoader.getSystemResources(String name); //多个
//或者
InputStream stream =  ClassLoader.getSystemResourceAsStream("javax/activation/DataSource.class");

// JDK下的,会加载classpath下的资源
 public static URL getSystemResource(String name) {
        ClassLoader system = getSystemClassLoader();
        if (system == null) {
            return getBootstrapResource(name);
        }
        return system.getResource(name);//调用下面的方法
    }
 public URL getResource(String name) {
    URL url;
    if (parent != null) {
        url = parent.getResource(name);
    } else {
        url = getBootstrapResource(name);
    }
    if (url == null) {
        url = findResource(name);
    }
    return url;
}

spring 中寻找多个classpath *:/META-INF/ ** .txt

去下是测试例子,你可以尝试断点跟踪
1.先获取目录(顶级的目录没有模式匹配的/META-INF/),加载目录里面的所有资源/META-INF/
2.在所有资源里面进行查找匹配URL资源中去选择匹配的(/META-INF/下面的文件夹),找出我们需要的资源

@Test
    public void PathMatchingResourcePatternResolver1()  throws Exception{
        ResourcePatternResolver loader = new PathMatchingResourcePatternResolver();
        Resource[] resources = loader.getResources("classpath:/META-INF/ *.txt");
        for(int i=0;i< resources.length;i++) {
            System.out.println(resources[i].getURL().getFile());
        }
    }

这个方法找到所有的资源,然后一个个的遍历,如果里面有JAR资源或者文件夹资源需要进行遍历处理哦
参考资源3-spring 资源

protected Resource[] findAllClassPathResources(String location) throws IOException {
        String path = location;
        if (path.startsWith("/")) {
            path = path.substring(1);
        }
        ClassLoader cl = getClassLoader();
        Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path)); //找到所有的资源哦
        Set<Resource> result = new LinkedHashSet<Resource>(16);
        while (resourceUrls.hasMoreElements()) {
            URL url = resourceUrls.nextElement();
            result.add(new UrlResource(url));// 就是将URL资源进行封装成spring resource 
        }
        return result.toArray(new Resource[result.size()]);
    }

得到了这样的URL资源后,判断这个资源是JAR类型的jar:file:…xx.jar!/META-INF/,然后进行遍历,会得到当前文件中的所有的资源,不光光是这个/META-INF/下的,然后进行处理,看看代码

/**
     * Find all resources in jar files that match the given location pattern
     * via the Ant-style PathMatcher.
     * @param rootDirResource the root directory as Resource(根路径的URl资源,被spring 抽象化Resource)
     * @param subPattern the sub pattern to match (below the root directory) 需要匹配的类型
     */
    protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, String subPattern)
            throws IOException {

        URLConnection con = rootDirResource.getURL().openConnection();
        JarFile jarFile;
        String jarFileUrl;
        String rootEntryPath;
        boolean newJarFile = false;

        if (con instanceof JarURLConnection) { //通过Connection的方式获取JARFile
            // Should usually be the case for traditional JAR files.
            JarURLConnection jarCon = (JarURLConnection) con;
            ResourceUtils.useCachesIfNecessary(jarCon);
            jarFile = jarCon.getJarFile();
            jarFileUrl = jarCon.getJarFileURL().toExternalForm();
            JarEntry jarEntry = jarCon.getJarEntry();//当前的入口地址是?就是!/后面的地址的信息
            rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
        }
        else {
            // No JarURLConnection -> need to resort to URL file parsing.
            // We'll assume URLs of the format "jar:path!/entry", with the protocol
            // being arbitrary as long as following the entry format.
            // We'll also handle paths with and without leading "file:" prefix.
            String urlFile = rootDirResource.getURL().getFile();
            int separatorIndex = urlFile.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
            if (separatorIndex != -1) {
                jarFileUrl = urlFile.substring(0, separatorIndex);
                rootEntryPath = urlFile.substring(separatorIndex + ResourceUtils.JAR_URL_SEPARATOR.length());
                jarFile = getJarFile(jarFileUrl);
            }
            else { // 通过JAR文件的形式
                jarFile = new JarFile(urlFile);
                jarFileUrl = urlFile;
                rootEntryPath = "";
            }
            newJarFile = true;
        }

        try {

            if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {
                // Root entry path must end with slash to allow for proper matching.
                // The Sun JRE does not return a slash here, but BEA JRockit does.
                rootEntryPath = rootEntryPath + "/";
            }
            Set<Resource> result = new LinkedHashSet<Resource>(8);
            for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
                JarEntry entry = entries.nextElement(); //获取文件的信息
                String entryPath = entry.getName();//文件的路径信息
                if (entryPath.startsWith(rootEntryPath)) {
                    String relativePath = entryPath.substring(rootEntryPath.length());
                    if (getPathMatcher().match(subPattern, relativePath)) {
                        result.add(rootDirResource.createRelative(relativePath));//根据root的URL路径创建一个相对的路径new URL(root, relativePath)
                    }
                }
            }
            return result;
        }
        finally {
            // Close jar file, but only if freshly obtained -
            // not from JarURLConnection, which might cache the file reference.
            if (newJarFile) {
                jarFile.close();
            }
        }
    }
posted @ 2022-10-12 17:57  Little_Monster-lhq  阅读(1468)  评论(0编辑  收藏  举报