001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.core.io; 018 019import java.net.MalformedURLException; 020import java.net.URL; 021import java.util.Collection; 022import java.util.LinkedHashSet; 023import java.util.Map; 024import java.util.Set; 025import java.util.concurrent.ConcurrentHashMap; 026 027import org.springframework.lang.Nullable; 028import org.springframework.util.Assert; 029import org.springframework.util.ClassUtils; 030import org.springframework.util.ResourceUtils; 031import org.springframework.util.StringUtils; 032 033/** 034 * Default implementation of the {@link ResourceLoader} interface. 035 * Used by {@link ResourceEditor}, and serves as base class for 036 * {@link org.springframework.context.support.AbstractApplicationContext}. 037 * Can also be used standalone. 038 * 039 * <p>Will return a {@link UrlResource} if the location value is a URL, 040 * and a {@link ClassPathResource} if it is a non-URL path or a 041 * "classpath:" pseudo-URL. 042 * 043 * @author Juergen Hoeller 044 * @since 10.03.2004 045 * @see FileSystemResourceLoader 046 * @see org.springframework.context.support.ClassPathXmlApplicationContext 047 */ 048public class DefaultResourceLoader implements ResourceLoader { 049 050 @Nullable 051 private ClassLoader classLoader; 052 053 private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4); 054 055 private final Map<Class<?>, Map<Resource, ?>> resourceCaches = new ConcurrentHashMap<>(4); 056 057 058 /** 059 * Create a new DefaultResourceLoader. 060 * <p>ClassLoader access will happen using the thread context class loader 061 * at the time of this ResourceLoader's initialization. 062 * @see java.lang.Thread#getContextClassLoader() 063 */ 064 public DefaultResourceLoader() { 065 this.classLoader = ClassUtils.getDefaultClassLoader(); 066 } 067 068 /** 069 * Create a new DefaultResourceLoader. 070 * @param classLoader the ClassLoader to load class path resources with, or {@code null} 071 * for using the thread context class loader at the time of actual resource access 072 */ 073 public DefaultResourceLoader(@Nullable ClassLoader classLoader) { 074 this.classLoader = classLoader; 075 } 076 077 078 /** 079 * Specify the ClassLoader to load class path resources with, or {@code null} 080 * for using the thread context class loader at the time of actual resource access. 081 * <p>The default is that ClassLoader access will happen using the thread context 082 * class loader at the time of this ResourceLoader's initialization. 083 */ 084 public void setClassLoader(@Nullable ClassLoader classLoader) { 085 this.classLoader = classLoader; 086 } 087 088 /** 089 * Return the ClassLoader to load class path resources with. 090 * <p>Will get passed to ClassPathResource's constructor for all 091 * ClassPathResource objects created by this resource loader. 092 * @see ClassPathResource 093 */ 094 @Override 095 @Nullable 096 public ClassLoader getClassLoader() { 097 return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader()); 098 } 099 100 /** 101 * Register the given resolver with this resource loader, allowing for 102 * additional protocols to be handled. 103 * <p>Any such resolver will be invoked ahead of this loader's standard 104 * resolution rules. It may therefore also override any default rules. 105 * @since 4.3 106 * @see #getProtocolResolvers() 107 */ 108 public void addProtocolResolver(ProtocolResolver resolver) { 109 Assert.notNull(resolver, "ProtocolResolver must not be null"); 110 this.protocolResolvers.add(resolver); 111 } 112 113 /** 114 * Return the collection of currently registered protocol resolvers, 115 * allowing for introspection as well as modification. 116 * @since 4.3 117 */ 118 public Collection<ProtocolResolver> getProtocolResolvers() { 119 return this.protocolResolvers; 120 } 121 122 /** 123 * Obtain a cache for the given value type, keyed by {@link Resource}. 124 * @param valueType the value type, e.g. an ASM {@code MetadataReader} 125 * @return the cache {@link Map}, shared at the {@code ResourceLoader} level 126 * @since 5.0 127 */ 128 @SuppressWarnings("unchecked") 129 public <T> Map<Resource, T> getResourceCache(Class<T> valueType) { 130 return (Map<Resource, T>) this.resourceCaches.computeIfAbsent(valueType, key -> new ConcurrentHashMap<>()); 131 } 132 133 /** 134 * Clear all resource caches in this resource loader. 135 * @since 5.0 136 * @see #getResourceCache 137 */ 138 public void clearResourceCaches() { 139 this.resourceCaches.clear(); 140 } 141 142 143 @Override 144 public Resource getResource(String location) { 145 Assert.notNull(location, "Location must not be null"); 146 147 for (ProtocolResolver protocolResolver : getProtocolResolvers()) { 148 Resource resource = protocolResolver.resolve(location, this); 149 if (resource != null) { 150 return resource; 151 } 152 } 153 154 if (location.startsWith("/")) { 155 return getResourceByPath(location); 156 } 157 else if (location.startsWith(CLASSPATH_URL_PREFIX)) { 158 return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); 159 } 160 else { 161 try { 162 // Try to parse the location as a URL... 163 URL url = new URL(location); 164 return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url)); 165 } 166 catch (MalformedURLException ex) { 167 // No URL -> resolve as resource path. 168 return getResourceByPath(location); 169 } 170 } 171 } 172 173 /** 174 * Return a Resource handle for the resource at the given path. 175 * <p>The default implementation supports class path locations. This should 176 * be appropriate for standalone implementations but can be overridden, 177 * e.g. for implementations targeted at a Servlet container. 178 * @param path the path to the resource 179 * @return the corresponding Resource handle 180 * @see ClassPathResource 181 * @see org.springframework.context.support.FileSystemXmlApplicationContext#getResourceByPath 182 * @see org.springframework.web.context.support.XmlWebApplicationContext#getResourceByPath 183 */ 184 protected Resource getResourceByPath(String path) { 185 return new ClassPathContextResource(path, getClassLoader()); 186 } 187 188 189 /** 190 * ClassPathResource that explicitly expresses a context-relative path 191 * through implementing the ContextResource interface. 192 */ 193 protected static class ClassPathContextResource extends ClassPathResource implements ContextResource { 194 195 public ClassPathContextResource(String path, @Nullable ClassLoader classLoader) { 196 super(path, classLoader); 197 } 198 199 @Override 200 public String getPathWithinContext() { 201 return getPath(); 202 } 203 204 @Override 205 public Resource createRelative(String relativePath) { 206 String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath); 207 return new ClassPathContextResource(pathToUse, getClassLoader()); 208 } 209 } 210 211}