转:http://blog.csdn.net/zl3450341/article/details/9306983
关于Spring Resource的资源类型以及继承体系我们已经在上一篇文件粗略的说了一下。Spring加载Resource文件是通过ResourceLoader来进行的,那么我们就先来看看ResourceLoader的继承体系,让我们对这个模块有一个比较系统的认知。
上图仅右边的继承体系,仅画至AbstractApplicationContext,由于ApplicationContext的继承体系,我们已经在前面章节给出,所以为了避免不必要的复杂性,本章继承体系就不引入ApplicationContext。
我们还是来关注本章的重点————classpath 与 classpath*以及通配符是怎么处理的
首先,我们来看下ResourceLoader的源码
- public interface ResourceLoader {
- /** Pseudo URL prefix for loading from the class path: "classpath:" */
- String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
- Resource getResource(String location);
- ClassLoader getClassLoader();
- }
我们发现,其实ResourceLoader接口只提供了classpath前缀的支持。而classpath*的前缀支持是在它的子接口ResourcePatternResolver中。
- public interface ResourcePatternResolver extends ResourceLoader {
- /**
- * Pseudo URL prefix for all matching resources from the class path: "classpath*:"
- * This differs from ResourceLoader's classpath URL prefix in that it
- * retrieves all matching resources for a given name (e.g. "/beans.xml"),
- * for example in the root of all deployed JAR files.
- * @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX
- */
- String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
- Resource[] getResources(String locationPattern) throws IOException;
- }
通过2个接口的源码对比,我们发现ResourceLoader提供 classpath下单资源文件的载入,而 ResourcePatternResolver提供了多资源文件的载入。
ResourcePatternResolver有一个实现类:PathMatchingResourcePatternResolver,那我们直奔主题,查看PathMatchingResourcePatternResolver的getResources()
- 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()));
- }
- }
- else {
- // Only look for a pattern after a prefix here
- // (to not get fooled by a pattern symbol in a strange prefix).
- int prefixEnd = 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)};
- }
- }
- }
由此我们可以看出在加载配置文件时,以是否是以classpath*开头分为2大类处理场景,每大类在又根据路径中是否包括通配符分为2小类进行处理,
处理的流程图如下:
从上图看,整个加载资源的场景有三条处理流程
- 以classpath*开头,但路径不包含通配符的
- protected Resource[] findAllClassPathResources(String location) throws IOException {
- String path = location;
- if (path.startsWith("/")) {
- path = path.substring(1);
- }
- Enumeration<URL> resourceUrls = getClassLoader().getResources(path);
- Set<Resource> result = new LinkedHashSet<Resource>(16);
- while (resourceUrls.hasMoreElements()) {
- URL url = resourceUrls.nextElement();
- result.add(convertClassLoaderURL(url));
- }
- return result.toArray(new Resource[result.size()]);
- }
我们可以看到,最关键的一句代码是:Enumeration<URL> resourceUrls = getClassLoader().getResources(path);
- public ClassLoader getClassLoader() {
- return getResourceLoader().getClassLoader();
- }
- public ResourceLoader getResourceLoader() {
- return this.resourceLoader;
- }
- //默认情况下
- public PathMatchingResourcePatternResolver() {
- this.resourceLoader = new DefaultResourceLoader();
- }
- public Enumeration<URL> getResources(String name) throws IOException {
- Enumeration[] tmp = new Enumeration[2];
- if (parent != null) {
- tmp[0] = parent.getResources(name);
- } else {
- tmp[0] = getBootstrapResources(name);
- }
- tmp[1] = findResources(name);
- return new CompoundEnumeration(tmp);
- }
- 不以classpath*开头,且路径不包含通配符的
- return new Resource[] {getResourceLoader().getResource(locationPattern)};
- public Resource getResource(String location) {
- Assert.notNull(location, "Location must not be null");
- if (location.startsWith(CLASSPATH_URL_PREFIX)) {
- return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
- }
- else {
- try {
- // Try to parse the location as a URL...
- URL url = new URL(location);
- return new UrlResource(url);
- }
- catch (MalformedURLException ex) {
- // No URL -> resolve as resource path.
- return getResourceByPath(location);
- }
- }
- }
其实很简单,如果以classpath开头,则创建为一个ClassPathResource,否则则试图以URL的方式加载资源,创建一个UrlResource.
- 路径包含通配符的
- protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
- //拿到能确定的目录,即拿到不包括通配符的能确定的路径 比如classpath*:/aaa/bbb/spring-*.xml 则返回classpath*:/aaa/bbb/ //如果是classpath*:/aaa/*/spring-*.xml,则返回 classpath*:/aaa/
- String rootDirPath = determineRootDir(locationPattern);
- //得到spring-*.xml
- String subPattern = locationPattern.substring(rootDirPath.length());
- //递归加载所有的根目录资源,要注意的是递归的时候又得考虑classpath,与classpath*的情况,而且还得考虑根路径中是否又包含通配符,参考上面那张流程图
- Resource[] rootDirResources = getResources(rootDirPath);
- Set<Resource> result = new LinkedHashSet<Resource>(16);
- //将根目录所有资源中所有匹配我们需要的资源(如spring-*)加载result中
- for (Resource rootDirResource : rootDirResources) {
- rootDirResource = resolveRootDirResource(rootDirResource);
- if (isJarResource(rootDirResource)) {
- result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
- }
- else if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
- result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
- }
- else {
- result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
- }
- }
- if (logger.isDebugEnabled()) {
- logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
- }
- return result.toArray(new Resource[result.size()]);
- }
- protected String determineRootDir(String location) {
- int prefixEnd = location.indexOf(":") + 1;
- int rootDirEnd = location.length();
- while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {
- rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1;
- }
- if (rootDirEnd == 0) {
- rootDirEnd = prefixEnd;
- }
- return location.substring(0, rootDirEnd);
- }
通配符支持“?”、“*”、“**”,注意通配符匹配不包括目录分隔符“/”:
“?”:匹配一个字符,如“config?.xml”将匹配“config1.xml”;
“*”:匹配零个或多个字符串,如“cn/*/config.xml”将匹配“cn/javass/config.xml”,但不匹配匹配“cn/config.xml”;而“cn/config-*.xml”将匹配“cn/config-dao.xml”;
“**”:匹配路径中的零个或多个目录,如“cn/**/config.xml”将匹配“cn /config.xml”,也匹配“cn/javass/spring/config.xml”;而“cn/javass/config-**.xml”将匹配“cn/javass/config-dao.xml”,即把“**”当做两个“*”处理。