为了管理方便,通常将Spring 的配置文件拆成多个,但也带来了复杂性的问题,不晓得它是如何加载的。
解析xml import
元素主要是org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#importBeanDefinitionResource()
方法为入口,看一下这个方法的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 String location = ele.getAttribute(RESOURCE_ATTRIBUTE); try { (resourceLocation.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX) || ResourceUtils.isUrl(resourceLocation))); absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute(); } if (absoluteLocation) { int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources); } else { Resource relativeResource = getReaderContext().getResource().createRelative(location); if (relativeResource.exists()) { importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource); actualResources.add(relativeResource); } else { String baseLocation = getReaderContext().getResource().getURL().toString(); importCount = getReaderContext().getReader().loadBeanDefinitions( StringUtils.applyRelativePath(baseLocation, location), actualResources); } }
在配置文件中引入其他文件,为了方便,本文件名为application.xml
,通常有以下几种写法:
直接写文件名 1 <import resource ="beans.xml" />
这种情况下表示的是相对路径,getReaderContext().getResource()
代表的是当前配置文件application.xml
,它是通过classpath
加载的,Resource.createRelative(location)
创建的资源类也是classpath
的,里面封装了classloader
,可以从Jar包里面加载对应的配置文件。这种解析方式也适用于beans.xml
还引入了<import resource="imports.xml"/>
的情形,创建的相对资源都是通过classloader
去加载的。
classpath:beans.xml 这种方式代表绝对路径,最终由org.springframework.core.io.support.PathMatchingResourcePatternResolver#getResources
方法处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Override 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)}; } } }
因为文件名中不含表达式,则通过org.springframework.core.io.DefaultResourceLoader#getResource
方法在classpath
中加载指定的文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Override public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); for (ProtocolResolver protocolResolver : this.protocolResolvers) { Resource resource = protocolResolver.resolve(location, this); if (resource != null) { return resource; } } if (location.startsWith("/")) { return getResourceByPath(location); } else if (location.startsWith(CLASSPATH_URL_PREFIX)) { //classpath:开头, 通过classpath加载 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); } } }
bean*.xml 此方式也是相对路径的形式,只会在引入此配置文件(即application.xml
)的目录中去查找符合此表达式的文件。
classpath:bean*.xml 与第二种方式类似,会走模式查找方法findPathMatchingResources(locationPattern)
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 protected Resource[] findPathMatchingResources(String locationPattern) throws IOException { String rootDirPath = determineRootDir(locationPattern); String subPattern = locationPattern.substring(rootDirPath.length()); Resource[] rootDirResources = getResources(rootDirPath); Set<Resource> result = new LinkedHashSet<Resource>(16 ); for (Resource rootDirResource : rootDirResources) { rootDirResource = resolveRootDirResource(rootDirResource); URL rootDirURL = rootDirResource.getURL(); if (equinoxResolveMethod != null ) { if (rootDirURL.getProtocol().startsWith("bundle" )) { rootDirURL = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null , rootDirURL); rootDirResource = new UrlResource(rootDirURL); } } if (rootDirURL.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) { result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirURL, subPattern, getPathMatcher())); } else if (ResourceUtils.isJarURL(rootDirURL) || isJarResource(rootDirResource)) { result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirURL, subPattern)); } 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()]); }
只有classpath对应的目录,只会在这个目录中去查找bean*.xml
对应的文件。
classpath*:beans.xml 跟方式4的区别在于findPathMatchingResources
方法中Resource[] rootDirResources = getResources(rootDirPath);
会查找到当前所有资源classpath目录以及jar包等。
总结:
方式|是否加载其他jar包等|是否模式匹配|是否相对路径 —|—|— 直接写文件名beans.xml
|是|否|相对路径classpath:beans.xml
|是|否|绝对路径bean*.xml
|否|是|相对路径classpath:bean*.xml
|否|是|绝对路径classpath*:bean*.xml
|是|是|绝对路径