首页 > 学院 > 开发设计 > 正文

Spring源码分析——资源访问利器Resource之接口和抽象类分析

2019-11-14 21:18:23
字体:
来源:转载
供稿:网友
SPRing源码分析——资源访问利器Resource之接口和抽象类分析

  从今天开始,一步步走上源码分析的路。刚开始肯定要从简单着手。我们先从java发展史上最强大的框架——Spring、、、旗下的资源抽象接口Resource开始吧。

  我看了好多分析Spring源码的,每每一开始就是Spring IOC、AOP、BeanFactory这样的Spring典型模块,实在看厌了,这些暂且留到以后。我的想法是,分析就分析别人没分析过的,或者以不同的角度来分析别人分析过的。

  可能很多用了Spring多年的程序员对Resource都了解有限,毕竟访问资源一般是搭建web工程框架的时候的事情。不过了解它也是非常有好处的。

  这个接口的作用是可以让我们更方便操纵底层资源。因为JDK操纵底层资源基本就是 java.net.URL 、java.io.File 、java.util.Properties这些。取资源基本是根据绝对路径或当前类的相对路径来取。从类路径或Web容器上下文中获取资源的时候也不方便。Resource接口提供了更强大的访问底层资源的能力。

  废话不多说,看源码之前先来看一下Resource的类结构。

一、类结构

一、Resource接口

  如图,Resouce接口并不是一个根接口,它继承了一个简单的父接口%20InputStreamSource,这个接口只有一个方法,用以返回一个输入流:

InputStream%20getInputStream()%20throws%20IOException;

  来,直接上Resource接口的源码,中文是我根据英文注释自己翻译的,如下:

public%20interface%20Resource%20extends%20InputStreamSource%20{%20%20%20%20boolean%20exists();%20%20%20%20%20%20//%20%20资源是否存在%20%20%20%20boolean%20isReadable();%20%20//%20%20资源是否可读%20%20%20%20boolean%20isOpen();%20%20%20%20%20%20//%20%20资源所代表的句柄是否被一个stream打开了%20%20%20%20URL%20getURL()%20throws%20IOException;%20%20%20//%20%20返回资源的URL的句柄%20%20%20%20URI%20getURI()%20throws%20IOException;%20%20%20//%20%20返回资源的URI的句柄%20%20%20%20File%20getFile()%20throws%20IOException;%20//%20%20返回资源的File的句柄%20%20%20%20long%20contentLength()%20throws%20IOException;%20%20%20//%20%20资源内容的长度%20%20%20%20long%20lastModified()%20throws%20IOException;%20%20%20%20//%20%20资源最后的修改时间%20%20%20%20Resource%20createRelative(String%20relativePath)%20throws%20IOException;%20%20%20//根据资源的相对路径创建新资源%20%20%20%20String%20getFilename();%20%20//%20%20资源的文件名%20%20%20%20String%20getDescription();%20%20%20//资源的描述}

  这个没什么好说的,继续!

二、抽象类AbstractResource

  对于任何的接口而言,这个直接抽象类是重中之重,里面浓缩了接口的大部分公共实现。翻译后如下:

public%20abstract%20class%20AbstractResource%20implements%20Resource%20{%20%20%20%20public%20boolean%20exists()%20{%20%20//判断文件是否存在,若判断过程产生异常(因为会调用SecurityManager来判断),就关闭对应的流%20%20%20%20%20%20%20%20try%20{%20%20%20%20%20%20%20%20%20%20%20%20return%20getFile().exists();%20%20%20%20%20%20%20%20}%20%20%20%20%20%20%20%20catch%20(IOException%20ex)%20{%20%20%20%20%20%20%20%20%20%20%20%20try%20{%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20InputStream%20is%20=%20getInputStream();%20%20//getInputStream()方法会被子类重写,%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20is.close();%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20true;%20%20%20%20%20%20%20%20%20%20%20%20}%20%20%20%20%20%20%20%20%20%20%20%20catch%20(Throwable%20isEx)%20{%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20false;%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20}%20%20%20%20%20%20%20%20}%20%20%20%20}%20%20%20%20public%20boolean%20isReadable()%20{%20%20//%20%20直接返回true,可读%20%20%20%20%20%20%20%20return%20true;%20%20%20%20}%20%20%20%20public%20boolean%20isOpen()%20{%20%20//%20%20直接返回false,未被打开%20%20%20%20%20%20%20%20return%20false;%20%20%20%20}%20%20%20%20public%20URL%20getURL()%20throws%20IOException%20{%20%20%20%20%20%20%20%20//%20%20留给子类重写%20%20%20%20%20%20%20%20throw%20new%20FileNotFoundException(getDescription()%20+%20"%20cannot%20be%20resolved%20to%20URL");%20%20%20%20}%20%20%20%20public%20URI%20getURI()%20throws%20IOException%20{%20%20%20//返回url%20%20%20%20%20%20%20%20URL%20url%20=%20getURL();%20%20%20%20%20%20%20%20try%20{%20%20%20%20%20%20%20%20%20%20%20%20return%20ResourceUtils.toURI(url);%20%20%20%20%20//将url格式化后返回%20%20%20%20%20%20%20%20}%20%20%20%20%20%20%20%20catch%20(URISyntaxException%20ex)%20{%20%20%20%20%20%20%20%20%20%20%20%20throw%20new%20NestedIOException("Invalid%20URI%20["%20+%20url%20+%20"]",%20ex);%20%20%20%20%20%20%20%20}%20%20%20%20}%20%20%20%20public%20File%20getFile()%20throws%20IOException%20{%20%20%20%20%20//%20%20留给子类重写%20%20%20%20%20%20%20%20throw%20new%20FileNotFoundException(getDescription()%20+%20"%20cannot%20be%20resolved%20to%20absolute%20file%20path");%20%20%20%20}%20%20%20%20//%20这个资源内容长度实际就是资源的字节长度,通过全部读取一遍来判断。这个方法调用起来很占资源啊!%20%20%20%20public%20long%20contentLength()%20throws%20IOException%20{%20%20%20%20%20%20%20%20%20%20%20InputStream%20is%20=%20this.getInputStream();%20%20%20%20%20%20%20%20Assert.state(is%20!=%20null,%20"resource%20input%20stream%20must%20not%20be%20null");%20%20%20//断言%20%20%20%20%20%20%20%20try%20{%20%20%20%20%20%20%20%20%20%20%20%20long%20size%20=%200;%20%20%20%20%20%20%20%20%20%20%20%20byte[]%20buf%20=%20new%20byte[255];%20%20%20%20%20%20%20%20%20%20%20%20int%20read;%20%20%20%20%20%20%20%20%20%20%20%20while((read%20=%20is.read(buf))%20!=%20-1)%20{%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20size%20+=%20read;%20%20%20%20%20%20%20%20%20%20%20%20}%20%20%20%20%20%20%20%20%20%20%20%20return%20size;%20%20%20%20%20%20%20%20}%20%20%20%20%20%20%20%20finally%20{%20%20%20%20%20%20%20%20%20%20%20%20try%20{%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20is.close();%20%20%20%20%20%20%20%20%20%20%20%20}%20%20%20%20%20%20%20%20%20%20%20%20catch%20(IOException%20ex)%20{%20%20%20%20%20%20%20%20%20%20%20%20}%20%20%20%20%20%20%20%20}%20%20%20%20}%20%20%20%20public%20long%20lastModified()%20throws%20IOException%20{%20%20%20%20//%20返回资源的最后修改时间%20%20%20%20%20%20%20%20long%20lastModified%20=%20getFileForLastModifiedCheck().lastModified();%20%20%20%20%20%20%20%20if%20(lastModified%20==%200L)%20{%20%20%20%20%20%20%20%20%20%20%20%20throw%20new%20FileNotFoundException(getDescription()%20+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20"%20cannot%20be%20resolved%20in%20the%20file%20system%20for%20resolving%20its%20last-modified%20timestamp");%20%20%20%20%20%20%20%20}%20%20%20%20%20%20%20%20return%20lastModified;%20%20%20%20}%20%20%20%20//%20这是Resource接口所没有的方法,注释的意思是“返回文件,给时间戳检查”,要求子类重写...%20%20%20%20protected%20File%20getFileForLastModifiedCheck()%20throws%20IOException%20{%20%20%20%20%20%20%20%20return%20getFile();%20%20%20%20}%20%20%20%20public%20Resource%20createRelative(String%20relativePath)%20throws%20IOException%20{%20%20%20//%20%20留给子类重写%20%20%20%20%20%20%20%20throw%20new%20FileNotFoundException("Cannot%20create%20a%20relative%20resource%20for%20"%20+%20getDescription());%20%20%20%20}%20%20%20%20public%20String%20getFilename()%20{%20%20//%20%20默认返回空(假设资源没有文件名),除非子类重写%20%20%20%20%20%20%20%20return%20null;%20%20%20%20}%20%20%20%20@Override%20%20%20%20public%20String%20toString()%20{%20%20%20%20%20//%20%20toString返回文件描述%20%20%20%20%20%20%20%20return%20getDescription();%20%20%20%20}%20%20%20%20@Override%20%20%20%20public%20boolean%20equals(Object%20obj)%20{%20%20%20%20//%20%20equals比较的就是2个资源描述是否一样%20%20%20%20%20%20%20%20return%20(obj%20==%20this%20||%20%20%20%20%20%20%20%20%20%20%20%20(obj%20instanceof%20Resource%20&&%20((Resource)%20obj).getDescription().equals(getDescription())));%20%20%20%20}%20%20%20%20@Override%20%20%20%20public%20int%20hashCode()%20{%20%20%20%20//%20%20返回资源描述的HashCode%20%20%20%20%20%20%20%20return%20getDescription().hashCode();%20%20%20%20%20%20%20}}

结论:

  1、增加了一个方法,protected%20File%20getFileForLastModifiedCheck()%20throws%20IOException,要求子类实现,如果子类未实现,那么直接返回资源文件。这个方法的具体作用,后面再看实现类。

  2、方法%20contentLength()%20,是一个很比较重量级的方法,它通过将资源全部读取一遍来判断资源的字节数。255字节的缓冲数组来读取。子类一般会重写。(调整一下缓冲数组的大小?)

  3、getDescription()%20是这个抽象类唯一没有实现的接口方法,留给子类去实现,资源文件默认的equals()、hashCode()%20都通过这个来判断。

  4、InputStreamSource这个祖先接口的唯一方法%20getInputStream()也没有被实现,留给子类。

三、Resource的子接口ContextResource和WritableResource

  这两个接口继承于Resource,拥有Resource的全部方法。其中,ContextResource接口增加了一个方法:

String%20getPathWithinContext();%20//%20%20返回上下文内的路径%20%20

  这个方法使得它的实现类有了返回当前上下文路径的能力。

  WritableResource接口增加了2个方法:

%20%20%20%20boolean%20isWritable();%20%20//%20%20是否可写%20%20%20%20OutputStream%20getOutputStream()%20throws%20IOException;%20//返回资源的写入流

  这个方法使得它的实现类拥有了写资源的能力。

四、重要的抽象类AbstractFileResolvingResource

  这个抽象类继承自AbstractResource,重写了AbstractResource的大部分方法。

/* * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package org.springframework.core.io;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.net.HttpURLConnection;import java.net.URI;import java.net.URL;import java.net.URLConnection;import org.springframework.util.ResourceUtils;/** * Abstract base class for resources which resolve URLs into File references, * such as {@link UrlResource} or {@link ClassPathResource}. * * <p>Detects the "file" protocol as well as the JBoss "vfs" protocol in URLs, * resolving file system references accordingly. * * @author Juergen Hoeller * @since 3.0 */public abstract class AbstractFileResolvingResource extends AbstractResource {    @Override    public File getFile() throws IOException { //  通过资源的URL得到资源本身,是文件就返回文件,否则返回描述        URL url = getURL();        if (url.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {            return VfsResourceDelegate.getResource(url).getFile();        }        return ResourceUtils.getFile(url, getDescription());    }    @Override    protected File getFileForLastModifiedCheck() throws IOException {  //从<压缩文件地址>中获取文件        URL url = getURL();        if (ResourceUtils.isJarURL(url)) {            URL actualUrl = ResourceUtils.extractJarFileURL(url);            if (actualUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {                return VfsResourceDelegate.getResource(actualUrl).getFile();            }            return ResourceUtils.getFile(actualUrl, "Jar URL");        }        else {            return getFile();        }    }    protected File getFile(URI uri) throws IOException {   //  通过资源uri获取文件        if (uri.getScheme().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {            return VfsResourceDelegate.getResource(uri).getFile();        }        return ResourceUtils.getFile(uri, getDescription());    }    @Override    public boolean exists() {  //判断资源是否存在,如果是文件Url,直接获取文件判断,否则,建立连接来判断。        try {            URL url = getURL();            if (ResourceUtils.isFileURL(url)) {                // Proceed with file system resolution...                return getFile().exists();            }            else {                // Try a URL connection content-length header...                URLConnection con = url.openConnection();                ResourceUtils.useCachesIfNecessary(con);                HttpURLConnection httpCon =                        (con instanceof HttpURLConnection ? (HttpURLConnection) con : null);                if (httpCon != null) {                    httpCon.setRequestMethod("HEAD");                    int code = httpCon.getResponseCode();                    if (code == HttpURLConnection.HTTP_OK) {                        return true;                    }                    else if (code == HttpURLConnection.HTTP_NOT_FOUND) {                        return false;                    }                }                if (con.getContentLength() >= 0) {                    return true;                }                if (httpCon != null) {                    // no HTTP OK status, and no content-length header: give up                    httpCon.disconnect();                    return false;                }                else {                    // Fall back to stream existence: can we open the stream?                    InputStream is = getInputStream();                    is.close();                    return true;                }            }        }        catch (IOException ex) {            return false;        }    }    @Override    public boolean isReadable() {  //  是否可读        try {            URL url = getURL();            if (ResourceUtils.isFileURL(url)) {                // Proceed with file system resolution...                File file = getFile();                return (file.canRead() && !file.isDirectory());            }            else {                return true;            }        }        catch (IOException ex) {            return false;        }    }    @Override    public long contentLength() throws IOException {        URL url = getURL();        if (ResourceUtils.isFileURL(url)) {            // Proceed with file system resolution...            return getFile().length();        }        else {            // Try a URL connection content-length header...            URLConnection con = url.openConnection();            ResourceUtils.useCachesIfNecessary(con);            if (con instanceof HttpURLConnection) {                ((HttpURLConnection) con).setRequestMethod("HEAD");            }            return con.getContentLength();
上一篇:Java 注释说明

下一篇:Java 中的System.exit

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表