1. 程式人生 > >Spring原始碼分析——資源訪問利器Resource之實現類分析

Spring原始碼分析——資源訪問利器Resource之實現類分析

http://doc.okbase.net/zrtqsk/archive/105869.html

  一、檔案系統資源 FileSystemResource

  檔案系統資源 FileSystemResource,資源以檔案系統路徑的方式表示。這個類繼承自AbstractResource,並實現了寫的介面WritableResource。類全稱為public class FileSystemResource extends AbstractResource implements WritableResource 。這個資源類是所有Resource實現類中,唯一一個實現了WritableResource介面的類。就是說,其他的類都不可寫入操作,都只能讀取。部分翻譯註釋後,原始碼如下:(以後不重要的原始碼我就摺疊起來) 

View Code

  結論:

  1、這個類由2個不可變的屬性 file 和 path ,本質上就是一個java.io.File 的包裝

  2、值得一提的是,與父類AbstractResource不同的是,這個類的 equals() 和 hashcode() 都通過屬性 path 來操作。

  測試:

public class FileSytemResourceTest {
    public static void main(String[] args) {
        String path = "E:/java/abc.txt";
        Resource resource = new FileSystemResource(path);
        System.out.println("resource1 : "+resource.getFilename());
        
        File f = new File("text.txt");
        Resource resource2 = new FileSystemResource(f);
        System.out.println("resource2 : "+resource2.getFilename());
    }
}

  結果:

resource1 : abc.txt
resource2 : text.txt

二、位元組陣列資源——ByteArrayResource

  位元組陣列資源ByteArrayResource,資源即,位元組陣列。

  這個類很簡單,也沒必要翻譯,僅僅是一個不可變的位元組陣列加一個不可變的描述字串的包裝,原始碼如下: 

/*
 * Copyright 2002-2012 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.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

/**
 * {@link Resource} implementation for a given byte array.
 * Creates a ByteArrayInputStreams for the given byte array.
 *
 * <p>Useful for loading content from any given byte array,
 * without having to resort to a single-use {@link InputStreamResource}.
 * Particularly useful for creating mail attachments from local content,
 * where JavaMail needs to be able to read the stream multiple times.
 *
 * @author Juergen Hoeller
 * @since 1.2.3
 * @see java.io.ByteArrayInputStream
 * @see InputStreamResource
 * @see org.springframework.mail.javamail.MimeMessageHelper#addAttachment(String, InputStreamSource)
 */
public class ByteArrayResource extends AbstractResource {

    private final byte[] byteArray;

    private final String description;


    /**
     * Create a new ByteArrayResource.
     * @param byteArray the byte array to wrap
     */
    public ByteArrayResource(byte[] byteArray) {
        this(byteArray, "resource loaded from byte array");
    }

    /**
     * Create a new ByteArrayResource.
     * @param byteArray the byte array to wrap
     * @param description where the byte array comes from
     */
    public ByteArrayResource(byte[] byteArray, String description) {
        if (byteArray == null) {
            throw new IllegalArgumentException("Byte array must not be null");
        }
        this.byteArray = byteArray;
        this.description = (description != null ? description : "");
    }

    /**
     * Return the underlying byte array.
     */
    public final byte[] getByteArray() {
        return this.byteArray;
    }


    /**
     * This implementation always returns {@code true}.
     */
    @Override
    public boolean exists() {
        return true;
    }

    /**
     * This implementation returns the length of the underlying byte array.
     */
    @Override
    public long contentLength() {
        return this.byteArray.length;
    }

    /**
     * This implementation returns a ByteArrayInputStream for the
     * underlying byte array.
     * @see java.io.ByteArrayInputStream
     */
    public InputStream getInputStream() throws IOException {
        return new ByteArrayInputStream(this.byteArray);
    }

    /**
     * This implementation returns the passed-in description, if any.
     */
    public String getDescription() {
        return this.description;
    }


    /**
     * This implementation compares the underlying byte array.
     * @see java.util.Arrays#equals(byte[], byte[])
     */
    @Override
    public boolean equals(Object obj) {
        return (obj == this ||
            (obj instanceof ByteArrayResource && Arrays.equals(((ByteArrayResource) obj).byteArray, this.byteArray)));
    }

    /**
     * This implementation returns the hash code based on the
     * underlying byte array.
     */
    @Override
    public int hashCode() {
        return (byte[].class.hashCode() * 29 * this.byteArray.length);
    }

}

View Code

  若需要操作描述一個位元組陣列,可以用這個資源類。ByteArrayResource可多次讀取陣列資源。

三、描述性資源——DescriptiveResource
  描述性資源DescriptiveResource,這個類更簡單,僅僅一個不可變的描述字串的包裝,原始碼如下: 

/*
 * Copyright 2002-2012 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.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
 * Simple {@link Resource} implementation that holds a resource description
 * but does not point to an actually readable resource.
 *
 * <p>To be used as placeholder if a {@code Resource} argument is
 * expected by an API but not necessarily used for actual reading.
 *
 * @author Juergen Hoeller
 * @since 1.2.6
 */
public class DescriptiveResource extends AbstractResource {

    private final String description;


    /**
     * Create a new DescriptiveResource.
     * @param description the resource description
     */
    public DescriptiveResource(String description) {
        this.description = (description != null ? description : "");
    }


    @Override
    public boolean exists() {
        return false;
    }

    @Override
    public boolean isReadable() {
        return false;
    }

    public InputStream getInputStream() throws IOException {
        throw new FileNotFoundException(
                getDescription() + " cannot be opened because it does not point to a readable resource");
    }

    public String getDescription() {
        return this.description;
    }


    /**
     * This implementation compares the underlying description String.
     */
    @Override
    public boolean equals(Object obj) {
        return (obj == this ||
            (obj instanceof DescriptiveResource && ((DescriptiveResource) obj).description.equals(this.description)));
    }

    /**
     * This implementation returns the hash code of the underlying description String.
     */
    @Override
    public int hashCode() {
        return this.description.hashCode();
    }

}

View Code

  若一個資源,僅僅有一個描述,非常抽象的這種情況,可以用這個資源類,它並沒有指向一個實際的可讀的資源。一般用的非常稀少。個人覺得用處不大。

四、輸入流資源——InputStreamResource
  輸入流資源InputStreamResource,是一個不可變InputStream的包裝和一個不可變的描述字串。此外還有一個私有成員變數Boolean read用於限制本資源的InputStream不可被重複獲取。
View Code 

/*
 * Copyright 2002-2012 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.IOException;
import java.io.InputStream;

/**
 * {@link Resource} implementation for a given InputStream. Should only
 * be used if no specific Resource implementation is applicable.
 * In particular, prefer {@link ByteArrayResource} or any of the
 * file-based Resource implementations where possible.
 *
 * <p>In contrast to other Resource implementations, this is a descriptor
 * for an <i>already opened</i> resource - therefore returning "true" from
 * {@code isOpen()}. Do not use it if you need to keep the resource
 * descriptor somewhere, or if you need to read a stream multiple times.
 *
 * @author Juergen Hoeller
 * @since 28.12.2003
 * @see ByteArrayResource
 * @see ClassPathResource
 * @see FileSystemResource
 * @see UrlResource
 */
public class InputStreamResource extends AbstractResource {

    private final InputStream inputStream;

    private final String description;

    private boolean read = false;


    /**
     * Create a new InputStreamResource.
     * @param inputStream the InputStream to use
     */
    public InputStreamResource(InputStream inputStream) {
        this(inputStream, "resource loaded through InputStream");
    }

    /**
     * Create a new InputStreamResource.
     * @param inputStream the InputStream to use
     * @param description where the InputStream comes from
     */
    public InputStreamResource(InputStream inputStream, String description) {
        if (inputStream == null) {
            throw new IllegalArgumentException("InputStream must not be null");
        }
        this.inputStream = inputStream;
        this.description = (description != null ? description : "");
    }


    /**
     * This implementation always returns {@code true}.
     */
    @Override
    public boolean exists() {
        return true;
    }

    /**
     * This implementation always returns {@code true}.
     */
    @Override
    public boolean isOpen() {
        return true;
    }

    /**
     * This implementation throws IllegalStateException if attempting to
     * read the underlying stream multiple times.
     */
    public InputStream getInputStream() throws IOException, IllegalStateException {
        if (this.read) {
            throw new IllegalStateException("InputStream has already been read - " +
                    "do not use InputStreamResource if a stream needs to be read multiple times");
        }
        this.read = true;
        return this.inputStream;
    }

    /**
     * This implementation returns the passed-in description, if any.
     */
    public String getDescription() {
        return this.description;
    }


    /**
     * This implementation compares the underlying InputStream.
     */
    @Override
    public boolean equals(Object obj) {
        return (obj == this ||
            (obj instanceof InputStreamResource && ((InputStreamResource) obj).inputStream.equals(this.inputStream)));
    }

    /**
     * This implementation returns the hash code of the underlying InputStream.
     */
    @Override
    public int hashCode() {
        return this.inputStream.hashCode();
    }

}

View Code

  簡單而言,這是一個InputStream的包裝類,這個包裝類指向的是一個已經開啟的資源,所以它的 isOpen()總是返回true。而且它不能重複獲取資源,只能讀取一次。關閉資源也只能通過其中的InputStream來關閉。個人認為,用處有限。

五、VFS資源——VfsResource
  vfs是Virtual File System虛擬檔案系統,也稱為虛擬檔案系統開關(Virtual Filesystem Switch).是Linux檔案系統對外的介面。任何要使用檔案系統的程式都必須經由這層介面來使用它。(摘自百度百科...)它能一致的訪問物理檔案系統、jar資源、zip資源、war資源等,VFS能把這些資源一致的對映到一個目錄上,訪問它們就像訪問物理檔案資源一樣,而其實這些資源不存在於物理檔案系統。
  
  這個資源類包裝類一個Object物件,所有的操作都是通過這個包裝的物件的反射來實現的。這裡就沒必要貼原始碼了。 
  可以參考下面的用法:  

@Test  
public void testVfsResourceForRealFileSystem() throws IOException {  
//1.建立一個虛擬的檔案目錄  
VirtualFile home = VFS.getChild("/home");  
//2.將虛擬目錄對映到物理的目錄  
VFS.mount(home, new RealFileSystem(new File("d:")));  
//3.通過虛擬目錄獲取檔案資源  
VirtualFile testFile = home.getChild("test.txt");  
//4.通過一致的介面訪問  
Resource resource = new VfsResource(testFile);  
if(resource.exists()) {  
        dumpStream(resource);  
}  
System.out.println("path:" + resource.getFile().getAbsolutePath());  
Assert.assertEquals(false, resource.isOpen());         
}  
@Test  
public void testVfsResourceForJar() throws IOException {  
//1.首先獲取jar包路徑  
    File realFile = new File("lib/org.springframework.beans-3.0.5.RELEASE.jar");  
    //2.建立一個虛擬的檔案目錄  
    VirtualFile home = VFS.getChild("/home2");  
    //3.將虛擬目錄對映到物理的目錄  
VFS.mountZipExpanded(realFile, home,  
TempFileProvider.create("tmp", Executors.newScheduledThreadPool(1)));  
//4.通過虛擬目錄獲取檔案資源  
    VirtualFile testFile = home.getChild("META-INF/spring.handlers");  
    Resource resource = new VfsResource(testFile);  
    if(resource.exists()) {  
            dumpStream(resource);  
    }  
    System.out.println("path:" + resource.getFile().getAbsolutePath());  
    Assert.assertEquals(false, resource.isOpen());  
}  

View Code


六、Portlet上下文資源——PortletContextResource

  Portlet是基於java的web元件,由portlet容器管理,並由容器處理請求,生產動態內容。這個資源類封裝了一個不可變的javax.portlet.PortletContext物件和一個不可變的String物件代表路徑。類中所有操作都基於這兩個屬性。PortletContextResource物件實現了ContextResource介面,實現了方法String getPathWithinContext(),即返回自身的path屬性。

  原始碼如下:

/*
 * Copyright 2002-2012 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.web.portlet.context;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import javax.portlet.PortletContext;

import org.springframework.core.io.AbstractFileResolvingResource;
import org.springframework.core.io.ContextResource;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.portlet.util.PortletUtils;

/**
 * {@link org.springframework.core.io.Resource} implementation for
 * {@link javax.portlet.PortletContext} resources, interpreting
 * relative paths within the portlet application root directory.
 *
 * <p>Always supports stream access and URL access, but only allows
 * {@code java.io.File} access when the portlet application archive
 * is expanded.
 *
 * @author Juergen Hoeller
 * @author John A. Lewis
 * @since 2.0
 * @see javax.portlet.PortletContext#getResourceAsStream
 * @see javax.portlet.PortletContext#getRealPath
 */
public class PortletContextResource extends AbstractFileResolvingResource implements ContextResource {

    private final PortletContext portletContext;

    private final String path;


    /**
     * Create a new PortletContextResource.
     * <p>The Portlet spec requires that resource paths start with a slash,
     * even if many containers accept paths without leading slash too.
     * Consequently, the given path will be prepended with a slash if it
     * doesn't already start with one.
     * @param portletContext the PortletContext to load from
     * @param path the path of the resource
     */
    public PortletContextResource(PortletContext portletContext, String path) {
        // check PortletContext
        Assert.notNull(portletContext, "Cannot resolve PortletContextResource without PortletContext");
        this.portletContext = portletContext;

        // check path
        Assert.notNull(path, "Path is required");
        String pathToUse = StringUtils.cleanPath(path);
        if (!pathToUse.startsWith("/")) {
            pathToUse = "/" + pathToUse;
        }
        this.path = pathToUse;
    }

    /**
     * Return the PortletContext for this resource.
     */
    public final PortletContext getPortletContext() {
        return this.portletContext;
    }

    /**
     * Return the path for this resource.
     */
    public final String getPath() {
        return this.path;
    }


    /**
     * This implementation checks {@code PortletContext.getResource}.
     * @see javax.portlet.PortletContext#getResource(String)
     */
    @Override
    public boolean exists() {
        try {
            URL url = this.portletContext.getResource(this.path);
            return (url != null);
        }
        catch (MalformedURLException ex) {
            return false;
        }
    }

    /**
     * This implementation delegates to {@code PortletContext.getResourceAsStream},
     * which returns {@code null} in case of a non-readable resource (e.g. a directory).
     * @see javax.portlet.PortletContext#getResourceAsStream(String)
     */
    @Override
    public boolean isReadable() {
        InputStream is = this.portletContext.getResourceAsStream(this.path);
        if (is != null) {
            try {
                is.close();
            }
            catch (IOException ex) {
                // ignore
            }
            return true;
        }
        else {
            return false;
        }
    }

    /**
     * This implementation delegates to {@code PortletContext.getResourceAsStream},
     * but throws a FileNotFoundException if not found.
     * @see javax.portlet.PortletContext#getResourceAsStream(String)
     */
    public InputStream getInputStream() throws IOException {
        InputStream is = this.portletContext.getResourceAsStream(this.path);
        if (is == null) {
            throw new FileNotFoundException("Could not open " + getDescription());
        }
        return is;
    }

    /**
     * This implementation delegates to {@code PortletContext.getResource},
     * but throws a FileNotFoundException if no resource found.
     * @see javax.portlet.PortletContext#getResource(String)
     */
    @Override
    public URL getURL() throws IOException {
        URL url = this.portletContext.getResource(this.path);
        if (url == null) {
            throw new FileNotFoundException(
                    getDescription() + " cannot be resolved to URL because it does not exist");
        }
        return url;
    }

    /**
     * This implementation resolves "file:" URLs or alternatively delegates to
     * {@code PortletContext.getRealPath}, throwing a FileNotFoundException
     * if not found or not resolvable.
     * @see javax.portlet.PortletContext#getResource(String)
     * @see javax.portlet.PortletContext#getRealPath(String)
     */
    @Override
    public File getFile() throws IOException {
        URL url = getURL();
        if (ResourceUtils.isFileURL(url)) {
            // Proceed with file system resolution...
            return super.getFile();
        }
        else {
            String realPath = PortletUtils.getRealPath(this.portletContext, this.path);
            return new File(realPath);
        }
    }

    @Override
    public Resource createRelative(String relativePath) {
        String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
        return new PortletContextResource(this.portletContext, pathToUse);
    }

    @Override
    public String getFilename() {
        return StringUtils.getFilename(this.path);
    }

    public String getDescription() {
        return "PortletContext resource [" + this.path + "]";
    }

    public String getPathWithinContext() {
        return this.path;
    }


    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (obj instanceof PortletContextResource) {
            PortletContextResource otherRes = (PortletContextResource) obj;
            return (this.portletContext.equals(otherRes.portletContext) && this.path.equals(otherRes.path));
        }
        return false;
    }

    @Override
    public int hashCode() {
        return this.path.hashCode();
    }

}

View Code

這個類非常簡單,並沒有什麼需要注意的。

七、Servlet上下文資源——ServletContextResource

  Servlet這個大家都知道。這個資源類是為了訪問Web容器上下文的資源而封裝的類,可以以相對於Web應用根目錄的路徑載入資源。這個資源類封裝了一個不可變的javax.servlet.ServletContext物件和一個不可變的String物件代表路徑。類中所有操作都基於這兩個屬性。PortletContextResource物件實現了ContextResource介面,實現了方法String getPathWithinContext(),即返回自身的path屬性。

  這個類的實現基本就是基於 this.servletContext.getResource(this.path) 或 this.servletContext.getResourceAsStream(this.path) 這兩個方法。

典型的,例如這個方法:

public InputStream getInputStream() throws IOException {
        InputStream is = this.servletContext.getResourceAsStream(this.path);
        if (is == null) {
            throw new FileNotFoundException("Could not open " + getDescription());
        }
        return is;
    }

  又如這個方法:

public URL getURL() throws IOException {
        URL url = this.servletContext.getResource(this.path);
        if (url == null) {
            throw new FileNotFoundException(
                    getDescription() + " cannot be resolved to URL because it does not exist");
        }
        return url;
    }

貼一下原始碼:

/*
 * Copyright 2002-2012 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.web.context.support;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import javax.servlet.ServletContext;

import org.springframework.core.io.AbstractFileResolvingResource;
import org.springframework.core.io.ContextResource;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.WebUtils;

/**
 * {@link org.springframework.core.io.Resource} implementation for
 * {@link javax.servlet.ServletContext} resources, interpreting
 * relative paths within the web application root directory.
 *
 * <p>Always supports stream access and URL access, but only allows
 * {@code java.io.File} access when the web application archive
 * is expanded.
 *
 * @author Juergen Hoeller
 * @since 28.12.2003
 * @see javax.servlet.ServletContext#getResourceAsStream
 * @see javax.servlet.ServletContext#getResource
 * @see javax.servlet.ServletContext#getRealPath
 */
public class ServletContextResource extends AbstractFileResolvingResource implements ContextResource {

    private final ServletContext servletContext;

    private final String path;


    /**
     * Create a new ServletContextResource.
     * <p>The Servlet spec requires that resource paths start with a slash,
     * even if many containers accept paths without leading slash too.
     * Consequently, the given path will be prepended with a slash if it
     * doesn't already start with one.
     * @param servletContext the ServletContext to load from
     * @param path the path of the resource
     */
    public ServletContextResource(ServletContext servletContext, String path) {
        // check ServletContext
        Assert.notNull(servletContext, "Cannot resolve ServletContextResource without ServletContext");
        this.servletContext = servletContext;

        // check path
        Assert.notNull(path, "Path is required");
        String pathToUse = StringUtils.cleanPath(path);
        if (!pathToUse.startsWith("/")) {
            pathToUse = "/" + pathToUse;
        }
        this.path = pathToUse;
    }

    /**
     * Return the ServletContext for this resource.
     */
    public final ServletContext getServletContext() {
        return this.servletContext;
    }

    /**
     * Return the path for this resource.
     */
    public final String getPath() {
        return this.path;
    }


    /**
     * This implementation checks {@code ServletContext.getResource}.
     * @see javax.servlet.ServletContext#getResource(String)
     */
    @Override
    public boolean exists() {
        try {
            URL url = this.servletContext.getResource(this.path);
            return (url != null);
        }
        catch (MalformedURLException ex) {
            return false;
        }
    }

    /**
     * This implementation delegates to {@code ServletContext.getResourceAsStream},
     * which returns {@code null} in case of a non-readable resource (e.g. a directory).
     * @see javax.servlet.ServletContext#getResourceAsStream(String)
     */
    @Override
    public boolean isReadable() {
        InputStream is = this.servletContext.getResourceAsStream(this.path);
        if (is != null) {
            try {
                is.close();
            }
            catch (IOException ex) {
                // ignore
            }
            return true;
        }
        else {
            return false;
        }
    }

    /**
     * This implementation delegates to {@code ServletContext.getResourceAsStream},
     * but throws a FileNotFoundException if no resource found.
     * @see javax.servlet.ServletContext#getResourceAsStream(String)
     */
    public InputStream getInputStream() throws IOException {
        InputStream is = this.servletContext.getResourceAsStream(this.path);
        if (is == null) {
            throw new FileNotFoundException("Could not open " + getDescription());
        }
        return is;
    }

    /**
     * This implementation delegates to {@code ServletContext.getResource},
     * but throws a FileNotFoundException if no resource found.
     * @see javax.servlet.ServletContext#getResource(String)
     */
    @Override
    public URL getURL() throws IOException {
        URL url = this.servletContext.getResource(this.path);
        if (url == null) {
            throw new FileNotFoundException(
                    getDescription() + " cannot be resolved to URL because it does not exist");
        }
        return url;
    }

    /**
     * This implementation resolves "file:" URLs or alternatively delegates to
     * {@code ServletContext.getRealPath}, throwing a FileNotFoundException
     * if not found or not resolvable.
     * @see javax.servlet.ServletContext#getResource(String)
     * @see javax.servlet.ServletContext#getRealPath(String)
     */
    @Override
    public File getFile() throws IOException {
        URL url = this.servletContext.getResource(this.path);
        if (url != null && ResourceUtils.isFileURL(url)) {
            // Proceed with file system resolution...
            return super.getFile();
        }
        else {
            String realPath = WebUtils.getRealPath(this.servletContext, this.path);
            return new File(realPath);
        }
    }

    /**
     * This implementation creates a ServletContextResource, applying the given path
     * relative to the path of the underlying file of this resource descriptor.
     * @see org.springframework.util.StringUtils#applyRelativePath(String, String)
     */
    @Override
    public Resource createRelative(String relativePath) {
        String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
        return new ServletContextResource(this.servletContext, pathToUse);
    }

    /**
     * This implementation returns the name of the file that this ServletContext
     * resource refers to.
     * @see org.springframework.util.StringUtils#getFilename(String)
     */
    @Override
    public String getFilename() {
        return StringUtils.getFilename(this.path);
    }

    /**
     * This implementation returns a description that includes the ServletContext
     * resource location.
     */
    public String getDescription() {
        return "ServletContext resource [" + this.path + "]";
    }

    public String getPathWithinContext() {
        return this.path;
    }


    /**
     * This implementation compares the underlying ServletContext resource locations.
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (obj instanceof ServletContextResource) {
            ServletContextResource otherRes = (ServletContextResource) obj;
            return (this.servletContext.equals(otherRes.servletContext) && this.path.equals(otherRes.path));
        }
        return false;
    }

    /**
     * This implementation returns the hash code of the underlying
     * ServletContext resource location.
     */
    @Override
    public int hashCode() {
        return this.path.hashCode();
    }

}

View Code

八、類路徑資源——ClassPathResource

  ClassPathResource這個資源類表示的是類路徑下的資源,資源以相對於類路徑的方式表示。這個資源類有3個成員變數,分別是一個不可變的相對路徑、一個類載入器、一個類物件。這個資源類可以相對於應用程式下的某個類或者相對於整個應用程式,但只能是其中之一,取決於構造方法有沒有傳入Class引數。

  這個類的實現基本也都是基於class的 getResourceAsStream(this.path) 或者 this.classLoader.getResourceAsStream(this.path) ,

我們來看一下它典型的getInputStream()方法:

public InputStream getInputStream() throws IOException {
        InputStream is;
        if (this.clazz != null) {
            is = this.clazz.getResourceAsStream(this.path);
        }
        else {
            is = this.classLoader.getResourceAsStream(this.path);
        }
        if (is == null) {
            throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
        }
        return is;
    }

可以看到,本資源類的clazz屬性存在,那麼資源相對於這個clazz類相對路徑的。如果不存在,那麼資源類就是相對於整個應用程式的。

這裡貼一下原始碼:

/*
 * Copyright 2002-2012 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.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
 * {@link Resource} implementation for class path resources.
 * Uses either a given ClassLoader or a given Class for loading resources.
 *
 * <p>Supports resolution as {@code java.io.File} if the class path
 * resource resides in the file system, but not for resources in a JAR.
 * Always supports resolution as URL.
 *
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @since 28.12.2003
 * @see ClassLoader#getResourceAsStream(String)
 * @see Class#getResourceAsStream(String)
 */
public class ClassPathResource extends AbstractFileResolvingResource {

    private final String path;

    private ClassLoader classLoader;

    private Class<?> clazz;


    /**
     * Create a new ClassPathResource for ClassLoader usage.
     * A leading slash will be removed, as the ClassLoader
     * resource access methods will not accept it.
     * <p>The thread context class loader will be used for
     * loading the resource.
     * @param path the absolute path within the class path
     * @see java.lang.ClassLoader#getResourceAsStream(String)
     * @see org.springframework.util.ClassUtils#getDefaultClassLoader()
     */
    public ClassPathResource(String path) {
        this(path, (ClassLoader) null);
    }

    /**
     * Create a new ClassPathResource for ClassLoader usage.
     * A leading slash will be removed, as the ClassLoader
     * resource access methods will not accept it.
     * @param path the absolute path within the classpath
     * @param classLoader the class loader to load the resource with,
     * or {@code null} for the thread context class loader
     * @see ClassLoader#getResourceAsStream(String)
     */
    public ClassPathResource(String path, ClassLoader classLoader) {
        Assert.notNull(path, "Path must not be null");
        String pathToUse = StringUtils.cleanPath(path);
        if (pathToUse.startsWith("/")) {
            pathToUse = pathToUse.substring(1);
        }
        this.path = pathToUse;
        this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
    }

    /**
     * Create a new ClassPathResource for Class usage.
     * The path can be relative to the given class,
     * or absolute within the classpath via a leading slash.
     * @param path relative or absolute path within the class path
     * @param clazz the class to load resources with
     * @see java.lang.Class#getResourceAsStream
     */
    public ClassPathResource(String path, Class<?> clazz) {
        Assert.notNull(path, "Path must not be null");
        this.path = StringUtils.cleanPath(path);
        this.clazz = clazz;
    }

    /**
     * Create a new ClassPathResource with optional ClassLoader and Class.
     * Only for internal usage.
     * @param path relative or absolute path within the classpath
     * @param classLoader the class loader to load the resource with, if any
     * @param clazz the class to load resources with, if any
     */
    protected ClassPathResource(String path, ClassLoader classLoader, Class<?> clazz) {
        this.path = StringUtils.cleanPath(path);
        this.classLoader = classLoader;
        this.clazz = clazz;
    }

    /**
     * Return the path for this resource (as resource path within the class path).
     */
    public final String getPath() {
        return this.path;
    }

    /**
     * Return the ClassLoader that this resource will be obtained from.
     */
    public final ClassLoader getClassLoader() {
        return (this.classLoader != null ? this.classLoader : this.clazz.getClassLoader());
    }

    /**
     * This implementation checks for the resolution of a resource URL.
     * @see java.lang.ClassLoader#getResource(String)
     * @see java.lang.Class#getResource(String)
     */
    @Override
    public boolean exists() {
        URL url;
        if (this.clazz != null) {
            url = this.clazz.getResource(this.path);
        }
        else {
            url = this.classLoader.getResource(this.path);
        }
        return (url != null);
    }

    /**
     * This implementation opens an InputStream for the given class path resource.
     * @see java.lang.ClassLoader#getResourceAsStream(String)
     * @see java.lang.Class#getResourceAsStream(String)
     */
    public InputStream getInputStream() throws IOException {
        InputStream is;
        if (this.clazz != null) {
            is = this.clazz.getResourceAsStream(this.path);
        }
        else {
            is = this.classLoader.getResourceAsStream(this.path);
        }
        if (is == null) {
            throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
        }
        return is;
    }

    /**
     * This implementation returns a URL for the underlying class path resource.
     * @see java.lang.ClassLoader#getResource(String)
     * @see java.lang.Class#getResource(String)
     */
    @Override
    public URL getURL() throws IOException {
        URL url;
        if (this.clazz != null) {
            url = this.clazz.getResource(this.path);
        }
        else {
            url = this.classLoader.getResource(this.path);
        }
        if (url == null) {
            throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
        }
        return url;
    }

    /**
     * This implementation creates a ClassPathResource, applying the given path
     * relative to the path of the underlying resource of this descriptor.
     * @see org.springframework.util.StringUtils#applyRelativePath(String, String)
     */
    @Override
    public Resource createRelative(String relativePath) {
        String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
        return new ClassPathResource(pathToUse, this.classLoader, this.clazz);
    }

    /**
     * This implementation returns the name of the file that this class path
     * resource refers to.
     * @see org.springframework.util.StringUtils#getFilename(String)
     */
    @Override
    public String getFilename() {
        return StringUtils.getFilename(this.path);
    }

    /**
     * This implementation returns a description that includes the class path location.
     */
    public String getDescription() {
        StringBuilder builder = new StringBuilder("class path resource [");
        String pathToUse = path;
        if (this.clazz != null && !pathToUse.startsWith("/")) {
            builder.append(ClassUtils.classPackageAsResourcePath(this.clazz));
            builder.append('/');
        }
        if (pathToUse.startsWith("/")) {
            pathToUse = pathToUse.substring(1);
        }
        builder.append(pathToUse);
        builder.append(']');
        return builder.toString();
    }

    /**
     * This implementation compares the underlying class path locations.
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (obj instanceof ClassPathResource) {
            ClassPathResource otherRes = (ClassPathResource) obj;
            return (this.path.equals(otherRes.path) &&
                    ObjectUtils.nullSafeEquals(this.classLoader, otherRes.classLoader) &&
                    ObjectUtils.nullSafeEquals(this.clazz, otherRes.clazz));
        }
        return false;
    }

    /**
     * This implementation returns the hash code of the underlying
     * class path location.
     */
    @Override
    public int hashCode() {
        return this.path.hashCode();
    }

}

View Code

可見,需要操作應用程式類路徑下的資源時,用這個資源類會非常方便。

九、Url資源——UrlResource

  UrlResource這個資源類封裝了可以以URL表示的各種資源。這個資源類有3個屬性,一個URI、一個URL,以及一個規範化後的URL,用於資源間的比較以及計算HashCode。

通過構造方法可以看到,這個資源類基本可以看作java.net.URL的封裝。這個資源類的很多方法也都是通過URL或URI操作的。

  若是操作URL資源,很明顯,這個類比單純的java.net.URL要好很多。這個類總體很簡單,重寫父類的方法不是很多,不必多說。

原始碼:

/*
 * Copyright 2002-2013 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.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;

import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;

/**
 * {@link Resource} implementation for {@code java.net.URL} locators.
 * Obviously supports resolution as URL, and also as File in case of
 * the "file:" protocol.
 *
 * @author Juergen Hoeller
 * @since 28.12.2003
 * @see java.net.URL
 */
public class UrlResource extends AbstractFileResolvingResource {

    /**
     * Original URI, if available; used for URI and File access.
     */
    private final URI uri;

    /**
     * Original URL, used for actual access.
     */
    private final URL url;

    /**
     * Cleaned URL (with normalized path), used for comparisons.
     */
    private final URL cleanedUrl;


    /**
     * Create a new UrlResource based on the given URI object.
     * @param uri a URI
     * @throws MalformedURLException if the given URL path is not valid
     */
    public UrlResource(URI uri) throws MalformedURLException {
        Assert.notNull(uri, "URI must not be null");
        this.uri = uri;
        this.url = uri.toURL();
        this.cleanedUrl = getCleanedUrl(this.url, uri.toString());
    }

    /**
     * Create a new UrlResource based on the given URL object.
     * @param url a URL
     */
    public UrlResource(URL url) {
        Assert.notNull(url, "URL must not be null");
        this.url = url;
        this.cleanedUrl = getCleanedUrl(this.url, url.toString());
        this.uri = null;
    }

    /**
     * Create a new UrlResource based on a URL path.
     * <p>Note: The given path needs to be pre-encoded if necessary.
     * @param path a URL path
     * @throws MalformedURLException if the given URL path is not valid
     * @see java.net.URL#URL(String)
     */
    public UrlResource(String path) throws MalformedURLException {
        Assert.notNull(path, "Path must not be null");
        this.uri = null;
        this.url = new URL(path);
        this.cleanedUrl = getCleanedUrl(this.url, path);
    }

    /**
     * Create a new UrlResource based on a URI specification.
     * <p>The given parts will automatically get encoded if necessary.
     * @param protocol the URL protocol to use (e.g. "jar" or "file" - without colon);
     * also known as "scheme"
     * @param location the location (e.g. the file path within that protocol);
     * also known as "scheme-specific part"
     * @throws MalformedURLException if the given URL specification is not valid
     * @see java.net.URI#URI(String, String, String)
     */
    public UrlResource(String protocol, String location) throws MalformedURLException  {
        this(protocol, location, null);
    }

    /**
     * Create a new UrlResource based on a URI specification.
     * <p>The given parts will automatically get encoded if necessary.
     * @param protocol the URL protocol to use (e.g. "jar" or "file" - without colon);
     * also known as "scheme"
     * @param location the location (e.g. the file path within that protocol);
     * also known as "scheme-specific part"
     * @param fragment the fragment within that location (e.g. anchor on an HTML page,
     * as following after a "#" separator)
     * @throws MalformedURLException if the given URL specification is not valid
     * @see java.net.URI#URI(String, String, String)
     */
    public UrlResource(String protocol, String location, String fragment) throws MalformedURLException  {
        try {
            this.uri = new URI(protocol, location, fragment);
            this.url = this.uri.toURL();
            this.cleanedUrl = getCleanedUrl(this.url, this.uri.toString());
        }
        catch (URISyntaxException ex) {
            MalformedURLException exToThrow = new MalformedURLException(ex.getMessage());
            exToThrow.initCause(ex);
            throw exToThrow;
        }
    }

    /**
     * Determine a cleaned URL for the given original URL.
     * @param originalUrl the original URL
     * @param originalPath the original URL path
     * @return the cleaned URL
     * @see org.springframework.util.StringUtils#cleanPath
     */
    private URL getCleanedUrl(URL originalUrl, String originalPath) {
        try {
            return new URL(StringUtils.cleanPath(originalPath));
        }
        catch (MalformedURLException ex) {
            // Cleaned URL path cannot be converted to URL
            // -> take original URL.
            return originalUrl;
        }
    }


    /**
     * This implementation opens an InputStream for the given URL.
     * It sets the "UseCaches" flag to {@code false},
     * mainly to avoid jar file locking on Windows.
     * @see java.net.URL#openConnection()
     * @see java.net.URLConnection#setUseCaches(boolean)
     * @see java.net.URLConnection#getInputStream()
     */
    public InputStream getInputStream() throws IOException {
        URLConnection con = this.url.openConnection();
        ResourceUtils.useCachesIfNecessary(con);
        try {
            return con.getInputStream();
        }
        catch (IOException ex) {
            // Close the HTTP connection (if applicable).
            if (con instanceof HttpURLConnection) {
                ((HttpURLConnection) con).disconnect();
            }
            throw ex;
        }
    }

    /**
     * This implementation returns the underlying URL reference.
     */
    @Override
    public URL getURL() throws IOException {
        return this.url;
    }

    /**
     * This implementation returns the underlying URI directly,
     * if possible.
     */
    @Override
    public URI getURI() throws IOException {
        if (this.uri != null) {
            return this.uri;
        }
        else {
            return super.getURI();
        }
    }

    /**
     * This implementation returns a File reference for the underlying URL/URI,
     * provided that it refers to a file in the file system.
     * @see org.springframework.util.ResourceUtils#getFile(java.net.URL, String)
     */
    @Override
    public File getFile() throws IOException {
        if (this.uri != null) {
            return super.getFile(this.uri);
        }
        else {
            return super.getFile();
        }
    }

    /**
     * This implementation creates a UrlResource, applying the given path
     * relative to the path of the underlying URL of this resource descriptor.
     * @see java.net.URL#URL(java.net.URL, String)
     */
    @Override
    public Resource createRelative(String relativePath) throws MalformedURLException {
        if (relativePath.startsWith("/")) {
            relativePath = relativePath.substring(1);
        }
        return new UrlResource(new URL(this.url, relativePath));
    }

    /**
     * This implementation returns the name of the file that this URL refers to.
     * @see java.net.URL#getFile()
     * @see java.io.File#getName()
     */
    @Override
    public String getFilename() {
        return new File(this.url.getFile()).getName();
    }

    /**
     * This implementation returns a description that includes the URL.
     */
    public String getDescription() {
        return "URL [" + this.url + "]";
    }


    /**
     * This implementation compares the underlying URL references.
     */
    @Override
    public boolean equals(Object obj) {
        return (obj == this ||
            (obj instanceof UrlResource && this.cleanedUrl.equals(((UrlResource) obj).cleanedUrl)));
    }

    /**
     * This implementation returns the hash code of the underlying URL reference.
     */
    @Override
    public int hashCode() {
        return this.cleanedUrl.hashCode();
    }

}

View Code

十、對資源編碼——EncodedResource

  EncodedResource並不是Resource的實現類,實際上它相當於一個工具類,用於給資源進行編碼。

  由於資源載入時預設採用系統編碼讀取資源內容,需要給Resource編碼時可以使用這個包裝工具類。它的核心為一個 java.io.Reader getReader()方法。

  這個類有一個不可變的Resource屬性、一個Charset屬性和一個表示字元的string屬性。後兩者存在一個即可。我們來看一下getReader()的實現:

public Reader getReader() throws IOException {
        if (this.charset != null) {
            return new InputStreamReader(this.resource.getInputStream(), this.charset);
        }
        else if (this.encoding != null) {
            return new InputStreamReader(this.resource.getInputStream(), this.encoding);
        }
        else {
            return new InputStreamReader(this.resource.getInputStream());
        }
    }

Resource這個資源介面大體就是這樣了。總體上感覺並不複雜。一般講Resource,基本都會講一個跟Resouce緊密相關的介面ResourceLoader,用來方便的載入Resouce。而由於ApplicationContext這些介面都是ResouceLoader的子介面,也實現類資源載入的功能。所以我打算分析完Spring容器類之後再來講這個。

閱讀Resouce原始碼最大的感悟就是,Resouce的工具類實在好強大,而且,Spring的編寫者對於JDK真的好熟悉,有機會必須分析分析JDK原始碼才行。