首页 > 程序开发 > 软件开发 > 其他 >

Web基础之使用URL访问资源

2016-11-09

在一些框架中经常看到使用URL访问项目中的资源,这篇文章简单的梳理了一下这个URL的相关内容,Web基础之使用URL访问资源。

前言

在一些框架中经常看到使用URL访问项目中的资源,这篇文章简单的梳理了一下这个URL的相关内容

正文

URL(Uniform Resource Locator)中文名为统一资源定位符
具体格式如下

protocol://host:port/path?query#ref

这里protocol可以是下面的内容:HTTP, HTTPS, FTP, 和File;port 为端口号;path为文件路径及文件名

//context是ServletContext
String absolutePath = context.getRealPath("/WEB-INF/classes/utf8.txt");
URL url = new URL("file",null,absolutePath); //absoltePath是普通的文件格式
URLConnection  connection = url.openConnection();
InputStream is = connection.getInputStream();
FileUtil.readFileWithInputStream(is, "utf8");

需要注意几点地方

1. getRealPath获取的是项目中的指定资源在文件系统中的完整路径,以“/”开头表示Web项目的根目录(http://host:port/webName 下的目录(URL))
2. 当前项目用maven多模块构建,读取的资源在一个maven模块中,也就是说部署到tomcat时,资源是在jar包,所以上面指定的路径会找不到,解决办法是获取jar的URL路径(用classloader加载获取jar的URL),然后使用JarURLConnection读取,或者将资源移动到maven项目的webapp所在的模块的src/main/resources中,在部署时会将资源放到classpath下
3. url.openConnection()的实例可能是FileURLConnection,HttpURLConnection,JarURLConnection还有其它几种,这里协议是file,所以返回的是FileURLConnection的实例,但是FileURLConnection这个类在java中不能找到,所以使用父类来引用,可以完成简单的操作
4. URL url = new URL(path)这里path必须是URL协议格式,如果是普通的文件路径,那么可以按照上面的3个参数构造函数创建对象也可以使用new File(resourceLocation).toURI().toURL()返回URL

获取jar包的URL

使用JarFile来完成jar操作

public static void obtainJarFile(String path) {
        try {
            JarFile jarFile = new JarFile(path); //jar包路径,创建一个jarFile对象
            System.out.println(String.format("%s", jarFile.getName()));
            Enumeration<jarentry> entrys = jarFile.entries();//jarFile提供操作jar包的方法
            while(entrys.hasMoreElements()) { //会从上到下,从外到里一个一个得到目录以及目录里面的文件
                JarEntry entry = entrys.nextElement();
                System.out.println(String.format("-%s",entry.getName()));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

需要注意的几点:

path是jar包所在的路径,可以是完整的文件系统路径,也可以是相对于工程的相对路径 枚举会将所有jar包的文件一一列举出来,包括目录和文件,如果只对文件感兴趣可以entry.isDirectory();来判断

使用classLoader来完成对jar的操作

需求:运行在tomcat容器中的项目,需要获取classpath下的config目录资源(包括WEB-INF/lib中jar里面的config资源,也包括WEB-INF/classes中的config目录的资源)
可行性:上面的需求通过分析发现可以用加载器加载资源来实现:因为web的加载器(WebAppClassLoader)会加载WEB-INF/lib和WEB-INF/classes目录下资源,只要传递相对于上面两个根目录的相对路径就能获取到需要的资源
抽象:将上面的业务抽象成算法如下

1. 得到当前的ClassLoader,因为运行在Tomcat容器中,所以是Tomcat提供的加载器
2. 通过外面给予的相对于加载器classpath的相对路径加载资源,使用getResources(path)来完成
3. 得到资源的URL可能是file:这种格式也可能是jar:格式,如果是file则将URL转成磁盘的文件系统路径,然后用File来读取资源。如果是jar则使用JarURLConnection 来获取JarFile来操作
4. 如果是jar:的URL,先获取jar包下的所有路径,在通过一一对比过滤得到指定目录下的文件,然后使用URL的openStream操作流读取资源
5. 如果是file:的URL,先将URL转成URI然后得到磁盘文件路径,使用File来读取指定目录下的文件资源

代码示例

public static void obtainJarFileWithClassLoader(String path) {
        try {
            ClassLoader loader = ClassLoaderUtil.getCurrentClassLoader();
            Enumeration<URL> urls = loader.getResources(path);
            while(urls.hasMoreElements()) {
                URL url = urls.nextElement();
                URLConnection  connection = url.openConnection();
                //使用父类URLConnection可以做一些简单的操作,
                //但是这里要做jar复杂一点的操作,明显不够用,所以转成JarURLConnection
                if(connection instanceof JarURLConnection) {
                    JarURLConnection jarConn = (JarURLConnection) connection;
                    URL jarPath = jarConn.getJarFileURL();//获取jar包所在的路径
                    System.out.println(String.format("jar包在文件系统中的路径:%s", jarPath.toExternalForm())); //toExternalForm作用URL转成String
                    JarFile jarFile = jarConn.getJarFile(); //获取jar包
                    JarEntry jarEntry = jarConn.getJarEntry(); //获取当前连接目录的路径
                    System.out.println(String.format("当前连接目录在jar的相对位置%s", jarEntry.getName()));
                    //获取jar包下的文件,并加载资源
                    Enumeration<JarEntry> entrys = jarFile.entries();
                    while(entrys.hasMoreElements()) {
                        JarEntry entry = entrys.nextElement();
                        if(!entry.isDirectory()) {
                            String relativePath = entry.getName();//文件在jar下的路径
                            if(relativePath.startsWith(jarEntry.getName())) { //只获取config目录下的资源,url包含了config目录,所以要将relativePath中的config截取掉
                                URL perResource = new URL(url,relativePath.substring(jarEntry.getName().length())); //使用基地址和相对URL创建
                                //使用URL加载资源
                                InputStream is = perResource.openStream();
                                FileUtil.readFileWithInputStream(is, "utf8");
                            }
                        }
                    }
                }else {
                    if(url.getProtocol().startsWith("file")) {
                        try {
                            //参考:http://blog.csdn.net/ocean1010/article/details/6114222
                            URI uri = new URI(url.getFile().replace(" ", "%20"));//将请求路径中的空格替换成%20
                            String resources = uri.getSchemeSpecificPart();//获取文件系统的路径,也就是将URL转成文件系统路径
                            File file = new File(resources); 
                            FileUtil.scanFileSystemDirectory(file,""); //显示这个目录结构
                        } catch (URISyntaxException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

ignore

1. 使用JarURLConnection得到当前jar文件:jarConn.getJarFile()
2. 得到jar包的URL路径:jarConn.getJarFileURL()或者jarConn.getJarFile().getName()
3. 得到当前相对路径(相对于加载器默认加载目录)下的目录,如外面传递的是“config/”它相对于WEB-INF/lib和WEB-INF/classes目录。JarEntry jarEntry = jarConn.getJarEntry(); 得到结果就是“config/”
4. public URL(URL context, String url) 使用基地址和相对URL创建。context是已经知道的URL,在context后面连接上指定的相对路径
5. 将URL转成文件系统路径:先转成URI,再使用getSchemeSpecificPart获取文件系统路径

加载jar包下的class

项目使用maven多模块构建,因此每个模块会打成jar包放到WEB-INF/lib下面,那么如何加载jar下面的class呢?
这里需要用到加载器,上面讲过了WEB的加载器会加载WEB-INF/lib下的资源,因此这里只要用WebappClassLoader加载器加载资源即可

/**
 - 加载jar包下的class
 - @param path org.easyutil.http.HttpClientUtil
 - @param clazz HttpClientUtil.class
 */
public static void loadJarClass(String path ,Class<?> clazz) {
        ClassLoader loader = ClassLoaderUtil.getCurrentClassLoader();
        try {
            //loader加载器和加载当前类FileUtil的加载器相同,所以这里可以赋值
            //参考:http://blog.sina.com.cn/s/blog_56d8ea90010127ks.html
            clazz = loader.loadClass(path);
            System.out.println(clazz.getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

ignore

Class<?>clazz = loader.loadClass(path); 必须保证加载左右两边的两个加载器相同。代码在(编译?执行?)时用当前加载器(从线程中获取?)加载Class尖括号中的类型的class,而后面loader又是我一个新的加载器,因此需要保证这两个加载器必须相同才不会报异常
只要选择对的加载器,就能加载指定目录下的资源
相关文章
最新文章
热点推荐