刚看完springMVC的源码,感觉spring的容器源码还是掌握不到位,所以打算写一点笔记再理一遍思路。
一.Resource
spring给资源提供了封装,根据不同的资源类型有不同的实现类,首先先看下UML图:
1.org.springframework.core.io.InputStreamSource
顶层接口,其中就定义了一个方法获取输入流:
InputStream getInputStream() throws IOException
2.org.springframework.core.io.Resource
Spring 框架所有资源的抽象和访问接口,定义了一些通用的方法。
public interface Resource extends InputStreamSource {
//资源是否存在
boolean exists();
//资源是否可读
default boolean isReadable() {
return true;
}
// 资源所代表的句柄是否被一个 stream 打开了
default boolean isOpen() {
return false;
}
// 是否为 File
default boolean isFile() {
return false;
}
//返回资源的 URL 的句柄
URL getURL() throws IOException;
// 返回资源的 URI 的句柄
URI getURI() throws IOException;
// 返回资源的 File 的句柄
File getFile() throws IOException;
//返回 ReadableByteChannel
default ReadableByteChannel readableChannel() throws IOException {
return java.nio.channels.Channels.newChannel(getInputStream());
}
//资源内容的长度
long contentLength() throws IOException;
// 资源最后的修改时间
long lastModified() throws IOException;
// 根据资源的相对路径创建新资源
Resource createRelative(String relativePath) throws IOException;
// 资源的文件名
@Nullable
String getFilename();
//资源的描述
String getDescription();
}
3.org.springframework.core.io.AbstractResource,是Resource接口的默认抽象实现:
public abstract class AbstractResource implements Resource {
@Override
public boolean exists() {
try {
//基于File进行判断
return getFile().exists();
}
catch (IOException ex) {
try {
//基于inputStream进行判断
InputStream is = getInputStream();
is.close();
return true;
}
catch (Throwable isEx) {
return false;
}
}
}
//表示可读
@Override
public boolean isReadable() {
return true;
}
//没有被打开
@Override
public boolean isOpen() {
return false;
}
//不是File
@Override
public boolean isFile() {
return false;
}
//抛出 FileNotFoundException 异常,交给子类实现
@Override
public URL getURL() throws IOException {
throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
}
//基于 getURL() 返回的 URL 构建 URI
@Override
public URI getURI() throws IOException {
URL url = getURL();
try {
return ResourceUtils.toURI(url);
}
catch (URISyntaxException ex) {
throw new NestedIOException("Invalid URI [" + url + "]", ex);
}
}
//不是File,自然不能返回File
@Override
public File getFile() throws IOException {
throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
}
//根据 getInputStream() 的返回结果构建 ReadableByteChannel
@Override
public ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
}
//这个资源内容长度实际就是资源的字节长度
@Override
public long contentLength() throws IOException {
InputStream is = getInputStream();
try {
long size = 0;
byte[] buf = new byte[255];
int read;
while ((read = is.read(buf)) != -1) {
size += read;
}
return size;
}
finally {
try {
is.close();
}
catch (IOException ex) {
}
}
}
//返回资源的最后修改时间
@Override
public long lastModified() throws IOException {
long lastModified = getFileForLastModifiedCheck().lastModified();
if (lastModified == 0L) {
throw new FileNotFoundException(getDescription() +
" cannot be resolved in the file system for resolving its last-modified timestamp");
}
return lastModified;
}
protected File getFileForLastModifiedCheck() throws IOException {
return getFile();
}
//子类实现相对路径
@Override
public Resource createRelative(String relativePath) throws IOException {
throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
}
//得到文件名
@Override
@Nullable
public String getFilename() {
return null;
}
@Override
public String toString() {
return getDescription();
}
//资源的描述相同也可以成立
@Override
public boolean equals(Object obj) {
return (obj == this ||
(obj instanceof Resource && ((Resource) obj).getDescription().equals(getDescription())));
}
@Override
public int hashCode() {
return getDescription().hashCode();
}
}
其余子类就不分析了,大同小异。
二.ResourceLoader
1.资源的加载都是通过ResourceLoader来完成的:
public interface ResourceLoader {
//classpath前缀,默认是“classpath:”
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
//根据路径获取资源:URL位置资源、ClassPath位置资源、相对路径资源等,主要实现是在DefaultResourceLoader
Resource getResource(String location);
//返回 ClassLoader 实例,对于想要获取 ResourceLoader 使用的 ClassLoader 用户来说,可以直接调用该方法来获取。
@Nullable
ClassLoader getClassLoader();
}
2、DefaultResourceLoader
public class DefaultResourceLoader implements ResourceLoader {
@Nullable
private ClassLoader classLoader;
//用户自定义协议资源解决策略
private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);
//资源缓存
private final Map<Class<?>, Map<Resource, ?>> resourceCaches = new ConcurrentHashMap<>(4);
//默认的无参构造
public DefaultResourceLoader() {
this.classLoader = ClassUtils.getDefaultClassLoader();
}
//传入构造函数classLoader
public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
}
public void setClassLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
@Nullable
public ClassLoader getClassLoader() {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
public void addProtocolResolver(ProtocolResolver resolver) {
Assert.notNull(resolver, "ProtocolResolver must not be null");
this.protocolResolvers.add(resolver);
}
/**
* Return the collection of currently registered protocol resolvers,
* allowing for introspection as well as modification.
* @since 4.3
*/
public Collection<ProtocolResolver> getProtocolResolvers() {
return this.protocolResolvers;
}
/**
* Obtain a cache for the given value type, keyed by {@link Resource}.
* @param valueType the value type, e.g. an ASM {@code MetadataReader}
* @return the cache {@link Map}, shared at the {@code ResourceLoader} level
* @since 5.0
*/
@SuppressWarnings("unchecked")
public <T> Map<Resource, T> getResourceCache(Class<T> valueType) {
return (Map<Resource, T>) this.resourceCaches.computeIfAbsent(valueType, key -> new ConcurrentHashMap<>());
}
/**
* Clear all resource caches in this resource loader.
* @since 5.0
* @see #getResourceCache
*/
public void clearResourceCaches() {
this.resourceCaches.clear();
}
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
//首先,通过 ProtocolResolver 来加载资源,这个类就是自定义的解析方法,自己定义location与资源的对应关系
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
//如果路径是以“/”开始的,返回ClassPathContextResource(DefaultResourceLoader的内部类,继承了ClassPathResource)
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 ,是则返回 FileUrlResource 类型的资源,否则返回 UrlResource 类型的资源
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
protected Resource getResourceByPath(String path) {
//构造 ClassPathContextResource 类型资源并返回
return new ClassPathContextResource(path, getClassLoader());
}
protected static class ClassPathContextResource extends ClassPathResource implements ContextResource {
public ClassPathContextResource(String path, @Nullable ClassLoader classLoader) {
super(path, classLoader);
}
//重写ContextResource接口方法
@Override
public String getPathWithinContext() {
return getPath();
}
@Override
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
return new ClassPathContextResource(pathToUse, getClassLoader());
}
}
}
3.FileSystemResourceLoader
public class FileSystemResourceLoader extends DefaultResourceLoader {
@Override
//在DefaultResourceLoader中,这个方法直接返回创建ClassPathContextResource,在这个类中重写了该方法,可以得到我们想要的资源类型
protected Resource getResourceByPath(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemContextResource(path);
}
private static class FileSystemContextResource extends FileSystemResource implements ContextResource {
public FileSystemContextResource(String path) {
super(path);
}
@Override
public String getPathWithinContext() {
return getPath();
}
}
}
4.ClassRelativeResourceLoader
//可以根据给定的class 所在包或者所在包的子包下加载资源。
public class ClassRelativeResourceLoader extends DefaultResourceLoader {
private final Class<?> clazz;
public ClassRelativeResourceLoader(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
this.clazz = clazz;
setClassLoader(clazz.getClassLoader());
}
@Override
protected Resource getResourceByPath(String path) {
return new ClassRelativeContextResource(path, this.clazz);
}
/**
* ClassPathResource that explicitly expresses a context-relative path
* through implementing the ContextResource interface.
*/
private static class ClassRelativeContextResource extends ClassPathResource implements ContextResource {
private final Class<?> clazz;
public ClassRelativeContextResource(String path, Class<?> clazz) {
super(path, clazz);
this.clazz = clazz;
}
@Override
public String getPathWithinContext() {
return getPath();
}
@Override
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
return new ClassRelativeContextResource(pathToUse, this.clazz);
}
}
}
5.ResourcePatternResolver
//它支持根据指定的资源路径匹配模式每次返回多个 Resource 实例
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}
6.PathMatchingResourcePatternResolver
//内置的资源加载器
private final ResourceLoader resourceLoader;
//Ant路径匹配器
private PathMatcher pathMatcher = new AntPathMatcher();
//默认构造
public PathMatchingResourcePatternResolver() {
this.resourceLoader = new DefaultResourceLoader();
}
//传入资源定位器
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
}
//传入类加载器
public PathMatchingResourcePatternResolver(@Nullable ClassLoader classLoader) {
this.resourceLoader = new DefaultResourceLoader(classLoader);
@Override
//委托给指定的资源定位器
public Resource getResource(String location) {
return getResourceLoader().getResource(location);
}
public ResourceLoader getResourceLoader() {
return this.resourceLoader;
}
@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
// 以 "classpath*:" 开头
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// 路径包含通配符
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// a class path resource pattern
return findPathMatchingResources(locationPattern);
// 路径不包含通配符
} else {
// all class path resources with the given name
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
// 不以 "classpath*:" 开头
} else {
// Generally only look for a pattern after a prefix here, // 通常只在这里的前缀后面查找模式
// and on Tomcat only after the "*/" separator for its "war:" protocol. 而在 Tomcat 上只有在 “*/ ”分隔符之后才为其 “war:” 协议
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
locationPattern.indexOf(':') + 1);
// 路径包含通配符
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
// 路径不包含通配符
} else {
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
其余方法不做分析,综上主要讲的是Spring中资源以及资源的定位器。