Resource 是 Spring 對資源得統(tǒng)一封裝接口,實現(xiàn)對各類資源得統(tǒng)一處理。
Resource 接口聲明了一些訪問資源得能力,
public interface Resource extends InputStreamSource { // 判斷資源是否存在 boolean exists(); // 判斷資源是否可讀,如果為 true,其內(nèi)容未必真得可讀,為 false,則一定不可讀 default boolean isReadable() { return exists(); } // 判斷資源是否已經(jīng)打開,主要針對流類型資源(InputStreamResource), default boolean isOpen() { return false; } // 判斷資源是否是文件 default boolean isFile() { return false; } // 獲取資源得 URL 地址,如果資源不能解析為 URL,則拋出異常 URL getURL() throws IOException; // 獲取資源得 URI 地址,如果資源不能解析為 URI,則拋出異常 URI getURI() throws IOException; // 獲取資源文件,如果資源不是文件,則拋出異常 File getFile() throws IOException; // NIO default ReadableByteChannel readableChannel() throws IOException { return Channels.newChannel(getInputStream()); } // 獲取資源內(nèi)容得長度 long contentLength() throws IOException; // 獲取資源蕞后更新時間 long lastModified() throws IOException; // 根據(jù)資源相對路徑創(chuàng)建新資源 Resource createRelative(String relativePath) throws IOException; // 獲取資源文件名 String getFilename(); // 獲取資源描述,通常是資源全路徑(實際文件名或者URL地址) String getDescription();}
Resource 接口繼承了 InputStreamSource 接口,這個接口只聲明了一個方法,就是獲取資源得 IO 流。
public interface InputStreamSource { // 獲取資源輸入IO流 InputStream getInputStream() throws IOException;}
Resource 擁有眾多得實現(xiàn)類,不同得實現(xiàn)類代表著不同得資源。接下來學習幾個常用得實現(xiàn)類得使用方法。
實現(xiàn)類 | 描述 |
ClassPathResource | 通過類路徑獲取資源 |
FileSystemResource | 通過文件系統(tǒng)獲取資源 |
UrlResource | 通過 URL 地址獲取遠程資源 |
ServletContextResource | 獲取 ServletContext 環(huán)境下得資源 |
ByteArrayResource | 獲取字節(jié)數(shù)組封裝得資源 |
InputStreamResource | 獲取輸入流封裝得資源 |
ClassPathResource
如果資源在項目內(nèi),可以通過類路徑讀取資源,主要通過如下兩種方式
ClassPathResource 其實就是對以上兩種方式進行了封裝,查看源碼,就可以知道
public class ClassPathResource extends AbstractFileResolvingResource { private final String path; private ClassLoader classLoader; private Class<?> clazz; public InputStream getInputStream() throws IOException { InputStream is; if (this.clazz != null) { is = this.clazz.getResourceAsStream(this.path); } else if (this.classLoader != null) { is = this.classLoader.getResourceAsStream(this.path); } else { is = ClassLoader.getSystemResourceAsStream(this.path); } if (is == null) { throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist"); } return is; }}
ClassPathResource 得使用方式如下所示
public class ClassPathResourceTest { public static void main(String[] args) throws Exception { // 只傳 path,相當于使用默認得 ClassLoader 進行加載 Resource resource1 = new ClassPathResource("com/test/hello.md"); System.out.println("resource1:" + resource1.getInputStream()); // path 前面加 "/",會自動去掉,與不加 "/" 是一樣得效果 Resource resource2 = new ClassPathResource("/com/test/hello.md"); System.out.println("resource2:" + resource2.getInputStream()); // 使用 Class 從 classpath 進行加載,path 前面加 "/" 與不加效果一樣 Resource resource3 = new ClassPathResource("/com/test/hello.md", ClassPathResourceTest.class); System.out.println("resource3:" + resource3.getInputStream()); // 使用 Class 得相對路徑進行加載 Resource resource4 = new ClassPathResource("../hello.md", ClassPathResourceTest.class); System.out.println("resource4:" + resource4.getInputStream()); // 使用指定得 ClassLoader 進行加載,從 classpath 根目錄進行加載 Resource resource5 = new ClassPathResource("com/test/hello.md", ClassPathResourceTest.class.getClassLoader()); System.out.println("resource5:" + resource5.getInputStream()); }}
FileSystemResource
如果資源本地文件系統(tǒng),可以通過文件路徑讀取資源
public class FileSystemResourceTest { public static void main(String[] args) throws Exception { // 使用文件路徑進行加載 Resource resource1 = new FileSystemResource("d:\\test.txt"); System.out.println("resource1:" + resource1.getInputStream()); // 使用 File 進行加載 Resource resource2 = new FileSystemResource(new File("d:\\test.txt")); System.out.println("resource2:" + resource2.getInputStream()); }}
查看源碼,可以知道 FileSystemResource 是基于 java.nio.file.Path 實現(xiàn)。
public class FileSystemResource extends AbstractResource implements WritableResource { private final String path; private final File file; private final Path filePath; public InputStream getInputStream() throws IOException { try { return Files.newInputStream(this.filePath); } catch (NoSuchFileException ex) { throw new FileNotFoundException(ex.getMessage()); } }}
UrlResource
如果資源在遠程服務器,則只能通過 URL 地址進行獲取。
public class FileSystemResourceTest { public static void main(String[] args) throws Exception { // 使用 Http 協(xié)議得 URL 地址進行加載 Resource resource1 = new UrlResource("感謝分享docs.spring.io/spring/docs/4.0.0.M1/spring-framework-reference/pdf/spring-framework-reference.pdf"); System.out.println("resource1:" + resource1.getInputStream()); // 使用 file 訪問本地文件系統(tǒng) Resource resource2 = new UrlResource("file:d:\\test.txt"); System.out.println("resource2:" + resource2.getInputStream()); }}
查看源碼中得實現(xiàn)
public class UrlResource extends AbstractFileResolvingResource { private final URI uri; private final URL url; private volatile URL cleanedUrl; 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; } }}
ByteArrayResource
資源即可以是文件,也可以是解析后得數(shù)據(jù)
public class ByteArrayResourceTest { public static void main(String[] args) throws Exception { ByteArrayResource resource1 = new ByteArrayResource("Hello".getBytes()); System.out.println("resource1:" + resource1.getInputStream()); }}
查看源碼,可以看到 getInputStream() 方法每次都會組裝一個全新得 ByteArrayInputStream 流
public class ByteArrayResource extends AbstractResource { private final byte[] byteArray; private final String description; public InputStream getInputStream() throws IOException { return new ByteArrayInputStream(this.byteArray); }}
InputStreamResource
使用 Stream 得 Resource,通過 getInputStream 方法進行資源加載,但是只能加載一次。
public class InputStreamResourceTest { public static void main(String[] args) throws Exception { InputStream is = new FileInputStream("d:\\test.txt"); InputStreamResource resource1 = new InputStreamResource(is); System.out.println("resource1:" + resource1.getInputStream()); is.close(); }}
構造方法傳入得就是 Stream,查看源碼,對 Stream 得使用進行控制。
public class InputStreamResource extends AbstractResource { private final InputStream inputStream; private final String description; private boolean read = false; 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; }}
通過 ResourceLoader 加載資源
Resource 雖然統(tǒng)一了各類資源得加載方式,但實現(xiàn)類眾多,為了更方便地使用 Resource,Spring 提供了 ResourceLoader 接口,專門用來加載 Resource。
public interface ResourceLoader {Resource getResource(String location);ClassLoader getClassLoader();}
ResourceLoader 得使用
public class ResourceLoaderTest { public static void main(String[] args) throws Exception { ResourceLoader loader = new DefaultResourceLoader(); Resource resource1 = loader.getResource("感謝分享特別baidu感謝原創(chuàng)分享者"); System.out.println("resource1 -- " + resource1.getClass().getSimpleName() + " -- " + resource1.getInputStream()); Resource resource2 = loader.getResource("classpath:com/test/hello3.md"); System.out.println("resource2 -- " + resource2.getClass().getSimpleName() + " -- " + resource2.getInputStream()); Resource resource3 = loader.getResource("com/test/hello3.md"); System.out.println("resource3 -- " + resource3.getClass().getSimpleName() + " -- " + resource3.getInputStream()); Resource resource4 = loader.getResource("file://d:\\test.txt"); System.out.println("resource4 -- " + resource4.getClass().getSimpleName() + " -- " + resource4.getInputStream()); }}
輸出如下,
resource1 -- UrlResource -- sun感謝原創(chuàng)分享者.特別protocol.http.HttpURLConnection$HttpInputStream等61e717c2resource2 -- ClassPathResource -- java.io.BufferedInputStream等3b764bceresource3 -- ClassPathContextResource -- java.io.BufferedInputStream等4c98385cresource4 -- FileUrlResource -- java.io.BufferedInputStream等73a8dfcc
查看源碼,可以清楚看到在 DefaultResourceLoader 中對 location 得處理邏輯。
public class DefaultResourceLoader implements ResourceLoader { private final Set<ProtocolResolver> protocolResolvers = new linkedHashSet<>(4); public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); // 使用 protocolResolvers 進行分析,但上例中并沒有設置,跳過 for (ProtocolResolver protocolResolver : getProtocolResolvers()) { Resource resource = protocolResolver.resolve(location, this); if (resource != null) { return resource; } } // 判斷是否 "/" 開頭,是則返回 ClassPathContextResource if (location.startsWith("/")) { return getResourceByPath(location); } // 判斷是否 "classpath" 開頭,是則返回 ClassPathResource else if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { // 如果都不是,則使用 URL 進行獲取 URL url = new URL(location); // 如果是系統(tǒng)文件,則返回 FileUrlResource,否則返回 UrlResource return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url)); } catch (MalformedURLException ex) { // 默認返回 ClassPathContextResource return getResourceByPath(location); } } } protected Resource getResourceByPath(String path) { return new ClassPathContextResource(path, getClassLoader()); }}
DefaultResourceLoader 只是 ResourceLoader 得一個默認實現(xiàn),ResourceLoader 還有一個繼承接口 ResourcePatternResolver,這個接口提供了基于 Ant 風格得通配符解析路徑得能力。
public interface ResourcePatternResolver extends ResourceLoader {String CLASSPATH_ALL_URL_PREFIX = "classpath*:";Resource[] getResources(String locationPattern) throws IOException;}
而 ApplicationContext 接口繼承了 ResourcePatternResolver 接口,所以,所有得 SpringContext 都可能通過 Ant 通配符解析加載資源。
public class ApplicationContextTest { public static void main(String[] args) throws Exception { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.refresh(); Resource[] resources = context.getResources("classpath:com/testa.xml
匹配 example 目錄及其子目錄下得 a.xml 文件
查看源碼,看看 Spring 是如何實現(xiàn)支持 Ant 通配符解析得。
getResources 得實現(xiàn)在 GenericApplicationContext 類中。GenericApplicationContext 類中有一個 ResourceLoader 成員變量,可以進行自定義設置,所以 GenericApplicationContext 使用得是組合得方式。
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry { private ResourceLoader resourceLoader; public Resource[] getResources(String locationPattern) throws IOException { if (this.resourceLoader instanceof ResourcePatternResolver) { return ((ResourcePatternResolver) this.resourceLoader).getResources(locationPattern); } return super.getResources(locationPattern); }}
如果 ResourceLoader 沒有設置,或者設置得不是 ResourcePatternResolver 得實現(xiàn)類,那么調(diào)用父類得 getResources 方法,也就是 AbstractApplicationContext 中得實現(xiàn)。
AbstractApplicationContext 構造方法中創(chuàng)建了一個默認得 PathMatchingResourcePatternResolver 對象,調(diào)用 getResources 方法進行資源加載時,則使用這個對象進行加載。另外需要注意得是,AbstractApplicationContext 也繼承了 DefaultResourceLoader 類,當調(diào)用 getResource 方法進行資源加載時,則是調(diào)用得 DefaultResourceLoader 中得實現(xiàn)。
public abstract class AbstractApplicationContext extends DefaultResourceLoaderimplements ConfigurableApplicationContext { private ResourcePatternResolver resourcePatternResolver; public AbstractApplicationContext() { this.resourcePatternResolver = getResourcePatternResolver(); } protected ResourcePatternResolver getResourcePatternResolver() { return new PathMatchingResourcePatternResolver(this); } public Resource[] getResources(String locationPattern) throws IOException { return this.resourcePatternResolver.getResources(locationPattern); } }
在 PathMatchingResourcePatternResolver 類中,getResource 方法得實現(xiàn)是回調(diào) resourceLoader 得該方法,resourceLoader 初始化得是 AbstractApplicationContext 得實例,所以實際調(diào)用得還是 DefaultResourceLoader 中得實現(xiàn)。getResources 方法中,對通配符得解析都在 findPathMatchingResources 方法中。解析得過程也不算復雜,就是先獲取通配符之前得目錄,然后通過文件系統(tǒng),一層層地輪詢匹配,得到所有得文件,再組裝成 FileSystemResource 對象。
public class PathMatchingResourcePatternResolver implements ResourcePatternResolver { private final ResourceLoader resourceLoader; private PathMatcher pathMatcher = new AntPathMatcher(); // 調(diào)用 ResourceLoader 得 getResource 方法,其實就是調(diào)用得 DefaultResourceLoader 類中得方法 public Resource getResource(String location) { return getResourceLoader().getResource(location); } public Resource[] getResources(String locationPattern) throws IOException { Assert.notNull(locationPattern, "Location pattern must not be null"); if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) { // 如果路徑中有通配符,則解析通配符 return findPathMatchingResources(locationPattern); } else { // 如果路徑中沒有通配符,直接加載即可 return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length())); } } else { int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 : locationPattern.indexOf(':') + 1); if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) { // 如果路徑中有通配符,則解析通配符 return findPathMatchingResources(locationPattern); } else { return new Resource[] {getResourceLoader().getResource(locationPattern)}; } } } protected Resource[] findPathMatchingResources(String locationPattern) throws IOException { // 獲取通配符之前得目錄 String rootDirPath = determineRootDir(locationPattern); String subPattern = locationPattern.substring(rootDirPath.length()); // 再調(diào) getResources 進行加載,rootDirPath 一定是沒有通配符得 Resource[] rootDirResources = getResources(rootDirPath); Set<Resource> result = new linkedHashSet<>(16); for (Resource rootDirResource : rootDirResources) { rootDirResource = resolveRootDirResource(rootDirResource); URL rootDirUrl = rootDirResource.getURL(); if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) { // vfs 開頭 result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher())); } else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) { // Jar 包目錄 result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern)); } else { // result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern)); } } return result.toArray(new Resource[0]); } protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern) throws IOException { File rootDir; try { rootDir = rootDirResource.getFile().getAbsoluteFile(); } catch (FileNotFoundException ex) { return Collections.emptySet(); } catch (Exception ex) { return Collections.emptySet(); } return doFindMatchingFileSystemResources(rootDir, subPattern); } protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException { // 枚舉目錄下所有得文件,并與 subPattern 進行匹配 Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern); Set<Resource> result = new linkedHashSet<>(matchingFiles.size()); for (File file : matchingFiles) { result.add(new FileSystemResource(file)); } return result; } }
蕞后感謝對創(chuàng)作者的支持一下,共同學習 Spring 框架源碼