前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【小家Spring】资源访问利器---Spring使用ResourceLoader接口便捷的获取资源(ResourcePatternResolver、ResourceLoaderAware)

【小家Spring】资源访问利器---Spring使用ResourceLoader接口便捷的获取资源(ResourcePatternResolver、ResourceLoaderAware)

作者头像
YourBatman
发布2019-09-03 16:21:58
2.1K0
发布2019-09-03 16:21:58
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦
前言

关于Spring Framework中资源的访问,上篇已经讲解了org.springframework.core.io.Resource接口,它有非常多的实现类,来针对不同的场景去规范统一的资源获取方式。

【小家Spring】资源访问利器—Spring提供的Resource接口以及它的常用子类源码分析

可能有小伙伴会想,既然有了Resource接口,为啥又搞出来一个ResourceLoader呢?

这其实我认为也是Spring优秀的特点的之一,它非常擅长用接口来规范操作,隔离一些操作。

Spring框架为了更方便的获取资源,尽量弱化程序员对各个Resource接口的实现类的感知(那么多实现类要程序员去记忆,其实也是不小的一个工作量),因此定义了另一个ResourceLoader接口。

ResourceLoader接口

先从接口本身下手,看看都提供了什么功能:

代码语言:javascript
复制
// @since 10.03.2004  小细节:Resource接口是@since 28.12.2003   所以这个接口晚了大概4个月的样子
public interface ResourceLoader {
	// 常量:classpath:
	// 其实ResourceLoader接口只提供了classpath前缀的支持。而classpath*的前缀支持是在它的子接口ResourcePatternResolver中
	String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
	
	// 这个方法可谓是核心方法:返回Resource实例
	// 它的目的很简单:就是希望程序员在使用时,不需要太在意使用的是哪个实例。面向接口操作即可
	// 至于选取哪个实例去操作,交给框架来完成
	Resource getResource(String location);
	
	// Expose the ClassLoader used by this ResourceLoader.
	// 暴露出ResourceLoader使用的类加载器~~~
	@Nullable
	ClassLoader getClassLoader();
}

从上可以看出:程序员在使用Spring容器时,可以不去过于计较底层Resource的实现,也不需要自己创建Resource实现类,而是直接使用**applicationContext.getResource()**,获取到bean容器本身的Resource,进而取到相关的资源信息。

所有ApplicationContext实例都实现了这个接口方法

从继承体系中可议看出,实现还是非常多的。但是本文先从另外一个角度出发,看看实现了Resource getResource(String location);方法的实现类:

很清晰的可以看到,真正实现了这个方法的只有三个类而已。而且GenericApplicationContext还继承自DefaultResourceLoader,而PathMatchingResourcePatternResolver实现的是子接口ResourcePatternResolver,因此放到最后说明。

DefaultResourceLoader 默认实现

它是一个非常重要的类,实现了ResourceLoader的所有功能。而且很多的类都会继承于它,再它的基础上进行扩展。

代码语言:javascript
复制
// @since 10.03.2004
public class DefaultResourceLoader implements ResourceLoader {
	
	// 这里面classLoader是允许为null的
	@Nullable
	private ClassLoader classLoader;
	// 这个特别重要:ProtocolResolver这个接口事Spring为开发者提供了自定义扩展接口(允许我们自己去介入参与到具体的获取资源的处理上,后面getResouce方法可议看出来)
	// ProtocolResolver接口Spring没有提供任何实现,开发者可议自己实现,从而参与到资源获取的路子上去
	// 备注:这个接口@since 4.3  所以只有Spring4.3后才有这个能力哦~~~
	private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);
	private final Map<Class<?>, Map<Resource, ?>> resourceCaches = new ConcurrentHashMap<>(4);

	// ClassLoader可以不指定(一般情况下也不需要指定)
	public DefaultResourceLoader() {
		this.classLoader = ClassUtils.getDefaultClassLoader();
	}
	public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
		this.classLoader = classLoader;
	}

	// 我们可以自己实现一个ProtocolResolver ,然后实现我们自己的获取资源的逻辑~~~下面会有示例
	public void addProtocolResolver(ProtocolResolver resolver) {
		Assert.notNull(resolver, "ProtocolResolver must not be null");
		this.protocolResolvers.add(resolver);
	}

	// @since 5.0  和ASM有关
	public <T> Map<Resource, T> getResourceCache(Class<T> valueType) {
		return (Map<Resource, T>) this.resourceCaches.computeIfAbsent(valueType, key -> new ConcurrentHashMap<>());
	}
	...
	// 这个是核心方法~~~
	@Override
	public Resource getResource(String location) {
		Assert.notNull(location, "Location must not be null");
		
		// 首先,Spring会看我们自己有没有实现自己的ProtocolResolver 若有实现,会先以我们自己的为准
		// 备注:它会把ResourceLoader传给开发者,这点特别重要~~~
		for (ProtocolResolver protocolResolver : this.protocolResolvers) {
			Resource resource = protocolResolver.resolve(location, this);
			if (resource != null) {
				return resource;
			}
		}
		
		// 如果以/打头,就交给getResourceByPath(),注意,它是一个protected方法,子类是可议复写的
		if (location.startsWith("/")) {
			return getResourceByPath(location);
		}
		// 如果以classpath:打头,毫无疑问,交给ClassPathResource
		// 需要注意的是,此处必须`classpath:`  区分大小写的哦
		else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
			return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
		}
		// 最后的处理方式:就是当作一个URL来处理
		else {
			try {
				// Try to parse the location as a URL...
				// 如果是文件类型,交给FileUrlResource(@since 5.0.2)
				URL url = new URL(location);
				return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
			}
			catch (MalformedURLException ex) {
				// No URL -> resolve as resource path.
				// fallback  相当于还是交给子类去弄吧
				return getResourceByPath(location);
			}
		}
	}
	
	// 它是一个protected方法,很多子类都有复写它。可议看到,默认的处理方式是:去classpth里查找这个资源
	// ClassPathContextResource事一个protected内部类 ` extends ClassPathResource implements ContextResource`
	protected Resource getResourceByPath(String path) {
		return new ClassPathContextResource(path, getClassLoader());
	}
	
}

下面简单给一个使用DefaultResourceLoader去加载资源的Demo:

代码语言:javascript
复制
    public static void main(String[] args) {
        DefaultResourceLoader bf = new DefaultResourceLoader();

        // 自己定义一个  如果是`fsx:`前缀的,那就也从classpath里面去查找这个资源~~~
        bf.addProtocolResolver((String location, ResourceLoader resourceLoader) -> {
            // 可以写自己的实现  返回值是Resource类型即可~~~
            if (location.startsWith("fsx:")) {
                return new ClassPathResource(location.substring("fsx:".length()));
            } else {
                return null; // 若返回null,Spring还是会继续往下走的~~~~
            }
        });

        Resource resource = bf.getResource("spring.properties");
        if (resource.exists()) {
            dumpStream(resource); //name=fangshixiang
        }
    }

再看看DefaultResourceLoader的子类们,实现就相对简单了:

ServletContextResourceLoader

显然它位于web包,和web相关。重写了getResourceByPath()

代码语言:javascript
复制
public class ServletContextResourceLoader extends DefaultResourceLoader {
	private final ServletContext servletContext;
	public ServletContextResourceLoader(ServletContext servletContext) {
		this.servletContext = servletContext;
	}
	// 从写此方法,用ServletContextResource去加载资源~~~~
	@Override
	protected Resource getResourceByPath(String path) {
		return new ServletContextResource(this.servletContext, path);
	}

}
FileSystemResourceLoader
代码语言:javascript
复制
public class FileSystemResourceLoader extends DefaultResourceLoader {
	// 如果以斜杠开头,那就去掉斜杠。并且使用FileSystemResource去装载
	@Override
	protected Resource getResourceByPath(String path) {
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		return new FileSystemContextResource(path);
	}
}
ClassRelativeResourceLoader

这个出现得稍微晚一点,@since 3.0,实现也是非常的简单。区别在于:setClassLoader(clazz.getClassLoader());并且使用了ClassPathResource去装载资源

AbstractApplicationContext

这个分支的实现,就很重要了。它直接和应用上下文有关了。

因为AbstractApplicationContext这个类之前非常详细的分析过,因此此处只讲解和ResourceLoader相关的方法

代码语言:javascript
复制
public abstract class AbstractApplicationContext extends DefaultResourceLoader
		implements ConfigurableApplicationContext {
		...
		// 它没有重写getResource()也没重写getResourceByPath()方法,直接使用的是父类`DefaultResourceLoader`的处理方式(毕竟人家是)Abstract实现,还没有具体指向的~~~
		// 但是有两个方法,可议引起注意~~~

	// ResourcePatternResolver 是 ResourceLoader的子接口
	protected ResourcePatternResolver getResourcePatternResolver() {
		return new PathMatchingResourcePatternResolver(this);
	}

	// ApplicationContext实现了ResourcePatternResolver接口~~~~
	@Override
	public Resource[] getResources(String locationPattern) throws IOException {
		return this.resourcePatternResolver.getResources(locationPattern);
	}

}
AbstractRefreshableApplicationContext

它没有对任何相关方法重写

AbstractRefreshableConfigApplicationContext
AbstractXmlApplicationContext

上面都没有重写相关方法

FileSystemXmlApplicationContext

它重写一个一个方法:getResourceByPath

代码语言:javascript
复制
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
	...
	// 显然根据类名也知道,它使用FileSystemResource去加载xml文件
	@Override
	protected Resource getResourceByPath(String path) {
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		return new FileSystemResource(path);
	}
	
}
ClassPathXmlApplicationContext

它没有重写任何方法。为何? 原因很简单:因为DefaultResourceLoader默认就是采用去Classpath里查找资源的方案,所以此处无需重写~~~

AbstractRefreshableWebApplicationContext

下面的子类都是和web相关了。所以就和ServletContext相关嘛~~~

代码语言:javascript
复制
// @since 1.1.3
public abstract class AbstractRefreshableWebApplicationContext extends AbstractRefreshableConfigApplicationContext
		implements ConfigurableWebApplicationContext, ThemeSource {
		...
	@Override
	protected Resource getResourceByPath(String path) {
		Assert.state(this.servletContext != null, "No ServletContext available");
		return new ServletContextResource(this.servletContext, path);
	}
	// ServletContextResourcePatternResolver是PathMatchingResourcePatternResolver的子类 
	@Override
	protected ResourcePatternResolver getResourcePatternResolver() {
		return new ServletContextResourcePatternResolver(this);
	}
	...
}
XmlWebApplicationContext

他没有自己的实现,但我们可议简单的回味一下它的类

代码语言:javascript
复制
public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {

	/** Default config location for the root context */
	public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
	/** Default prefix for building a config location for a namespace */
	public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
	/** Default suffix for building a config location for a namespace */
	public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
	...
}
AnnotationConfigWebApplicationContext

它是注解驱动的,所以也就没有必要冲洗方法喽 @since 3.0

GenericApplicationContext
代码语言:javascript
复制
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
	...
	
	// 它重写一个逻辑都是:加入调用者自己指定了resourceLoader,那就以调用者为准
	@Override
	public Resource getResource(String location) {
		if (this.resourceLoader != null) {
			return this.resourceLoader.getResource(location);
		}
		return super.getResource(location);
	}
	@Override
	public Resource[] getResources(String locationPattern) throws IOException {
		if (this.resourceLoader instanceof ResourcePatternResolver) {
			return ((ResourcePatternResolver) this.resourceLoader).getResources(locationPattern);
		}
		return super.getResources(locationPattern);
	}
	...
}

如图的几个子类。除了带web的重写了一下(使用ServletContextResourceServletContextResourcePatternResolver),其余子类都没有重写的必要

子接口 ResourcePatternResolver 解析

上面已经介绍了ResourceLoader的实现,那么接下来针对于子接口进一步讨论。其实它使用得反而更加的广泛,也更加的强大。从名字中可以看出,它支持**Pattern**,支持正则的模式匹配,非常的强大~~~

用于解析资源文件的策略接口,其特殊的地方在于,它应该提供带有*号这种通配符的资源路径。 此接口是ResourceLoader接口的拓展接口。

代码语言:javascript
复制
// @since 1.0.2 显然它出现的时机晚于ResourceLoader接口
public interface ResourcePatternResolver extends ResourceLoader {

	// 与ResourceLoader中classpath不同的地方在于,此前缀会在所有的JAR包的根目录下搜索指定文件。  非常的强大
	String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
	// 返回指定路径下所有的资源对象。
	// 返回的对象数组有Set的语义,也就是说,`对于同一个资源,只返回一个资源对象`
	Resource[] getResources(String locationPattern) throws IOException;
}

先来个Demo,简单的感受下classpath*的威力:

代码语言:javascript
复制
     public static void main(String[] args) throws IOException {
        ResourceLoader resourceLoader = new DefaultResourceLoader();
        Resource resource = resourceLoader.getResource("classpath:META-INF/spring.factories");
        // 因为`classpath:`只在本工程内查找,所以肯定找不到 spring.factories
        System.out.println(resource.exists()); //false


        PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        Resource[] resources = resourcePatternResolver.getResources("classpath*:META-INF/spring.factories");
        // 它会去找所有的jar包的类路径开始查找,所以现在是可议找到多个的~~~
        System.out.println(resources.length); //2
        System.out.println(Arrays.asList(resources));
        //[URL [jar:file:/E:/repository/org/springframework/spring-beans/5.0.6.RELEASE/spring-beans-5.0.6.RELEASE.jar!/META-INF/spring.factories],
        //URL [jar:file:/E:/repository/org/springframework/spring-test/5.0.6.RELEASE/spring-test-5.0.6.RELEASE.jar!/META-INF/spring.factories]]

        // 还能使用Ant风格进行匹配~~~  太强大了:
        resources = resourcePatternResolver.getResources("classpath*:META-INF/*.factories");
        System.out.println(resources); // 能匹配上所有了路径下,`META-INF/*.factories`匹配上的所有文件
        resources = resourcePatternResolver.getResources("classpath*:com/fsx/**/*.class");
        System.out.println(resources.length); //42 相当于把我当前项目所有的类都拿出来了
    }
PathMatchingResourcePatternResolver

该实现类是本文的重中之重。

它是基于模式匹配的,默认使用org.springframework.util.AntPathMatcher进行路径匹配,它除了支持ResourceLoader支持的前缀外,还额外支持classpath*:用于加载所有匹配的类路径Resource

代码语言:javascript
复制
public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
	// 内部持有一个resourceLoader的引用
	private final ResourceLoader resourceLoader;
	// 我们发现,它内部使用是AntPathMatcher进行匹配的(Spring内部AntPathMatcher是PathMatcher接口的唯一实现。
	//如果你想改变此匹配规则,你可以自己实现(当然这是完全没必要的))
	private PathMatcher pathMatcher = new AntPathMatcher();
	
	// 默认使用的DefaultResourceLoader  当然也可以指定
	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);
	}
	...
	
	// 显然,我们也可以自己指定解析器,默认使用AntPathMatcher
	public void setPathMatcher(PathMatcher pathMatcher) {
		Assert.notNull(pathMatcher, "PathMatcher must not be null");
		this.pathMatcher = pathMatcher;
	}
	// 最终是委托给ResourceLoader去做了
	@Override
	public Resource getResource(String location) {
		return getResourceLoader().getResource(location);
	}

	// 这个是核心方法~~~~~~~
	@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
				// 事patter,那就交给这个方法,这个是个核心方法   这里传入的是locationPattern
				return findPathMatchingResources(locationPattern);
			}
			else {
				// all class path resources with the given name
				// 如果不是pattern,那就完全匹配。去找所有的path下的匹配上的就成~~~
				return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
			}
		}
		
		// 不是以`classpath*:`打头的~~~~
		else {
			// 支持到tomcat的war:打头的方式~~~
			int prefixEnd = (locationPattern.startsWith("war:") ?  locationPattern.indexOf("*/") + 1 :
					locationPattern.indexOf(':') + 1);
			if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
				return findPathMatchingResources(locationPattern);
			}
			
			// 如果啥都不打头,那就当作一个正常的处理,委托给ResourceLoader直接去处理
			else {
				// a single resource with the given name
				return new Resource[] {getResourceLoader().getResource(locationPattern)};
			}
		}
	}

	// 根据Pattern去匹配资源~~~~
	protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
		// 定位到跟文件夹地址。比如locationPattern=classpath:META-INF/spring.factories  得到classpath*:META-INF/
		// 若是classpath*:META-INF/ABC/*.factories 得到的就是 classpath*:META-INF/ABC/
		// 简单的说,就是截取第一个不是patter的地方的前半部分
		String rootDirPath = determineRootDir(locationPattern);
		// 后半部分  这里比如就是:*.factories
		String subPattern = locationPattern.substring(rootDirPath.length());
		
		// 这个递归就厉害了,继续调用了getResources("classpath*:META-INF/")方法
		// 相当于把该文件夹匹配的所有的资源(注意:可能会比较多的),最后在和patter匹配即可~~~~
		// 比如此处:只要jar里面有META-INF目录的  都会被匹配进来~~~~~~
		Resource[] rootDirResources = getResources(rootDirPath);
		Set<Resource> result = new LinkedHashSet<>(16);
		for (Resource rootDirResource : rootDirResources) {
			// resolveRootDirResource是留给子类去复写的。但是Spring没有子类复写此方法,默认实现是啥都没做~~~
			rootDirResource = resolveRootDirResource(rootDirResource);
			URL rootDirUrl = rootDirResource.getURL();
			
			// 这个if就一般不看了  是否为了做兼容~~~
			if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
				URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
				if (resolvedUrl != null) {
					rootDirUrl = resolvedUrl;
				}
				rootDirResource = new UrlResource(rootDirUrl);
			}


			// 支持vfs协议(JBoss)~~~
			if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
				result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
			}
		
			// 是否是jar文件或者是jar资源(显然大多数情况下都是此情况~~~)
			else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
				// 把rootDirUrl, subPattern都交给doFindPathMatchingJarResources去处理
				result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
			}
			
			// 不是Jar文件(那就是本工程里字的META-INF目录~~~)
			// 那就没啥好说的,直接给个subPattern去匹配吧   注意这个方法名是File,上面是jar
			else {
				result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
			}
		}
		
		// 最终转换为数组返回。 注意此处的result是个set,是有去重的效果的~~~
		return result.toArray(new Resource[0]);
	}

	protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, URL rootDirURL, String subPattern) {
		... // 这个方法就源码不细说了,
		//Find all resources in jar files that match the given location pattern
		// 就是去这个jar里面去找所有的资源(默认利用Ant风格匹配~)
		//此处用到了`java.util.jar.JarFile`、`ZipFile`、`java.net.JarURLConnection`等等
		// 路径匹配:getPathMatcher().match(subPattern, relativePath) 使用的此方法去校对
	}

	// Find all resources in the file system that match the given location pattern
	// 简单的说,这个就是在我们自己的项目里找~~~~
	protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
			throws IOException {

		// rootDir:最终是个绝对的路径地址,带盘符的。来代表META-INF这个文件夹~~~
		File rootDir;
		try {
			rootDir = rootDirResource.getFile().getAbsoluteFile();
		} catch (IOException ex) {
			return Collections.emptySet();
		}
		// FileSystem  最终是根据此绝对路径 去文件系统里找
		return doFindMatchingFileSystemResources(rootDir, subPattern);
	}

	// 这个比较简单:就是把该文件夹所有的文件都拿出来dir.listFiles(),然后一个个去匹配呗~~~~
	// 备注:子类`ServletContextResourcePatternResolver`复写了此方法~~~~~
	protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
		if (logger.isDebugEnabled()) {
			logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
		}
		Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
		Set<Resource> result = new LinkedHashSet<>(matchingFiles.size());
		for (File file : matchingFiles) {]
			// 最终用FileSystemResource把File包装成一个Resource~
			result.add(new FileSystemResource(file));
		}
		return result;
	}
	...
}

至此,整个过程核心就是遍历、递归、匹配、路径的处理。说难也难,说容易也就这么些事。可能路径处理方面是比较复杂也是最容易出错的地方

ServletContextResourcePatternResolver

显然它是web下,用于获取资源的。上面已经介绍了web容器下都是使用它去获取资源~~(ServletContextResourceLoader) 它复写了这个方法:

代码语言:javascript
复制
public class ServletContextResourcePatternResolver extends PathMatchingResourcePatternResolver {
	...
	
	// 一般会使用这个构造函数构造~~~
	public ServletContextResourcePatternResolver(ServletContext servletContext) {
		super(new ServletContextResourceLoader(servletContext));
	}

	@Override
	protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
			throws IOException {
		
		// 如果你这个Resource是web的,那就交给它处理
		// doRetrieveMatchingServletContextResources是核心处理方法~~~
		if (rootDirResource instanceof ServletContextResource) {
			ServletContextResource scResource = (ServletContextResource) rootDirResource;
			ServletContext sc = scResource.getServletContext();
			String fullPattern = scResource.getPath() + subPattern;
			Set<Resource> result = new LinkedHashSet<>(8);
			doRetrieveMatchingServletContextResources(sc, fullPattern, scResource.getPath(), result);
			return result;
		}
		else {
			return super.doFindPathMatchingFileResources(rootDirResource, subPattern);
		}
	}
	
}
ApplicationContext

ApplicationContext这个接口就再熟悉不过了,它代表整个Spring应用的上下文。它也继承自ResourcePatternResolver,赋予了上下文更便捷的处理Resource的能力。

代码语言:javascript
复制
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
		MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
		...
}

大部分ApplicationContext的实现类已经在上面都说明了,这里只需要简单的说说WebApplicationContext这个分支~~~

WebApplicationContext

这个是web环境下的容器的父接口。org.springframework.web.context.WebApplicationContext

代码语言:javascript
复制
public interface WebApplicationContext extends ApplicationContext {
	String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
	
	String SCOPE_REQUEST = "request";
	String SCOPE_SESSION = "session";
	String SCOPE_APPLICATION = "application";
	String SERVLET_CONTEXT_BEAN_NAME = "servletContext";

	String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";
	String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";

	// 这个接口相当于只新增了这个一个接口~~~
	@Nullable
	ServletContext getServletContext();
}

ResourceLoaderAware

这是Spring为了方便我们获得Resource而提供的感知接口~~~

Spring会自动调用实现了ResourceLoaderAware接口类方法:serResourceLoader(),将ApplicationContext的ResourceLoader注入进去,之后对它getResource(),就获取了系统的Resource了,可谓非常的贴心~

总结

applicationContext.getResource()会采用和ApplicationContext相同的策略来访问资源:

  • ClassPathXmlApplicationContext 则底层Resource是ClassPathResource实例
  • FileSystemXmlApplicationContext 则底层Resource是FileSystemResource实例
  • XmlWebApplicationContext 则底层Resource是ServletContextResource实例

所以我们一般这么使用就行,对开发者而言,不用关心具体使用的是那种Resource:

代码语言:javascript
复制
Resource resource = applicationContext.getResource("classpath:spring-mvc.xml");
// 或者

// 即使创建的时候使用的是`classpath:`,但是最后getResource()实际上使用的还是FileSystemResource 这点需要注意~~~
applicationContext=new FileSystemXmlApplicationContext("classpath:bean.xml");
applicationContext.getResource("book.xml");

在写一个资源路径时,提倡使用classpath*作为前缀(如果确定去哪儿加载,还是使用classpath:也行)以查找所有Jar的根目录。使用无占位符的文件名如/beans.xml来确切的表名想要引入的文件名(效率最高~)

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年04月25日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • ResourceLoader接口
    • DefaultResourceLoader 默认实现
      • AbstractApplicationContext
        • AbstractRefreshableApplicationContext
        • GenericApplicationContext
        • PathMatchingResourcePatternResolver
        • ApplicationContext
        • 总结
    • 子接口 ResourcePatternResolver 解析
    • ResourceLoaderAware
    相关产品与服务
    容器服务
    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档