//构建XmlBeanDefinitionReader并开始加载配置文件
protected void org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
//创建XmlBeanDefinitionReader,并关联BeanFactory,用于将解析好的BeanDefinition注册到BeanDefinition中
XmlBeanDefinitionReader
beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
//设置环境变量实例
beanDefinitionReader.setEnvironment(getEnvironment());
//设置资源加载器,XmlWebApplicationContext实现了ResourceLoader
beanDefinitionReader.setResourceLoader(this);
//设置xml校验资源
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
//初始化XmlBeanDefinitionReader,目前为空方法,可以由子类实现
initBeanDefinitionReader(beanDefinitionReader);
//加载配置文件
loadBeanDefinitions(beanDefinitionReader);
}
上面一段代码主要就是创建了XmlBeanDefinitionReader用于读取配置文件,XmlBeanDefinitionContext的类图如下:
[外链
EnvironmentCapable(定义获取Environment的能力)
BeanDefinitionReader(定义获取BeanDefinitionRegister,ResoureLoader以及加载BeanDefinition的能力)
AbstractBeanDefinitionReader(模板类,定义loadBeanDefinitions模板方法)
XmlBeanDefinitionReader (完整的实现类)
//获取配置文件,由ServletContextResourcePatternResolver的实例调用,这个实例与XmlWebApplicationContext关联,在创建context的时候由其父类的AbstractApplicationContext构造器调用AbstractRefreshableWebApplicationContext的getResourcePatternResolver方法得到
public Resource[] org.springframework.core.io.support.PathMatchingResourcePatternResolver.getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
//判断当前配置路径是否由classpath*:开头
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// a class path resource (multiple resources for same name possible)
//使用AntPathMatcher判断当前配置路径是否符合ant通配符模式
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;
//判断是否符合ant路径模式
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是如何获取资源的,第一种通过通配符的方式获取
//通过通配符的方式获取资源
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
//获取不是通配符的跟路径,比如classpath:spring/springContext-*.xml,那么它的根dir就是classpath:spring,如果是classpath:spring/config/springContext-*.xml,那么根路径就是classpath:spring/config
String rootDirPath = determineRootDir(locationPattern);
//获取具有通配符的路径,也就是去掉rootDirPath的,如果是classpath:spring/springContext-*.xml那么subPattern就是springContext-*.xml
String subPattern = locationPattern.substring(rootDirPath.length());
//获取根路径对应的资源,如果是classpath*:开头的,那么将会获取所有加载器下的资源
Resource[] rootDirResources = getResources(rootDirPath);
Set<Resource> result = new LinkedHashSet<Resource>(16);
for (Resource rootDirResource : rootDirResources) {
//用于解析OSGi的url路径,这里不管它
rootDirResource = resolveRootDirResource(rootDirResource);
//判断是否为vfs(虚拟文件系统)类型的url,内部使用VfsUtils反射获取资源,这个获取资源的类是jboss提供的一个vfs资源读取工具
if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
}
//判断是否为压缩类型的url协议,比如jar,vfszip开头的
else if (isJarResource(rootDirResource)) {
result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
}
else {
//寻找普通的文件,匹配模式使用AntPathMatcher进行匹配
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
if (logger.isDebugEnabled()) {
logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
return result.toArray(new Resource[result.size()]);
}
主要看下获取普通文件的方式,对于vfs,spring封装了一个VfsUtils获取资源,而VfsUitls内部发射的是jboss提供的vfs访问工具(org.jboss.vfs.VFS)。对于jar,是通AntPathMatcher去匹配jar的entryName来获取资源的,下面我们主要分析一下获取普通系统文件的方式
/**
* fullPattern 表示绝对路径的通配符模式,比如D:/java_project/learn_sprin* g_source/classes/spring/springContext-*.xml
*
* dir 表示根目录,前面说到这个根目录为没有通配符的那个目录,比如D:/java* _project/learn_sprin* g_source/classes/spring/
*
* result 一个用于存储发现并且能够匹配通配符的file文件集合
*/
protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("Searching directory [" + dir.getAbsolutePath() +
"] for files matching pattern [" + fullPattern + "]");
}
//列出当前根目录下所有的文件
File[] dirContents = dir.listFiles();
if (dirContents == null) {
if (logger.isWarnEnabled()) {
logger.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
}
return;
}
//递归的方式选择文件,并用AntPathMatcher去匹配,如果匹配就存到result集合中
for (File content : dirContents) {
String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
if (!content.canRead()) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
"] because the application is not allowed to read the directory");
}
}
else {
doRetrieveMatchingFiles(fullPattern, content, result);
}
}
//AntPatchMatcher
if (getPathMatcher().match(fullPattern, currPath)) {
result.add(content);
}
}
}
现在来分析下AntPathMatcher是如何进行匹配的
protected boolean doMatch(String pattern, String path, boolean fullMatch, Map<String, String> uriTemplateVariables) {
//如果两者开头匹配/不一致,那么就是不匹配,返回false
if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
return false;
}
//根据/进行分割成数组
String[] pattDirs = tokenizePattern(pattern);
String[] pathDirs = tokenizePath(path);
int pattIdxStart = 0;
int pattIdxEnd = pattDirs.length - 1;
int pathIdxStart = 0;
int pathIdxEnd = pathDirs.length - 1;
//如果没有**,那么就会进行全匹配
// Match all elements up to the first **
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
String pattDir = pattDirs[pattIdxStart];
if ("**".equals(pattDir)) {
break;
}
//此处会发生*?{}字符的替换,替换成正则表达式进行匹配
if (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
return false;
}
pattIdxStart++;
pathIdxStart++;
}
//如果path数组的元素被耗尽
if (pathIdxStart > pathIdxEnd) {
// Path is exhausted, only match if rest of pattern is * or **'s
//并且pattern数组的元素也被耗尽,那么说明两者长度一样,那么只要匹配查看他们的结束符是否相同,相同就返回true,不相同就返回false,匹配 srping/springContext-*.xml vs spring/springContext-dao.xml的情况
if (pattIdxStart > pattIdxEnd) {
return (pattern.endsWith(this.pathSeparator) ? path.endsWith(this.pathSeparator) :
!path.endsWith(this.pathSeparator));
}
//如果path数组被耗尽,而pattern未被好景,并且不要求全匹配,那么直接返回true,匹配 spring/config/springContext-*.xml vs spring/config的情况
if (!fullMatch) {
return true;
}
//如果path数组耗尽,而pattern还未耗尽,还是全匹配的模式下,那么判断pattern最后一个元素是否为*,path最后一个字符是否为/,用于匹配 spring/config/* vs spring/config/这种情况
if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {
return true;
}
//如果path数组被耗尽,而pattern还未被耗尽,还是全匹配的模式下,那么就需要判断从当前pattIdxStart开始是否全是**,否则返回false,用于匹配 spring/config/**[/**/**] vs spring/config这种情况
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (!pattDirs[i].equals("**")) {
return false;
}
}
return true;
}
//如果path数组未被耗尽,而pattern数组被耗尽,那么肯定是不匹配的
else if (pattIdxStart > pattIdxEnd) {
// String not exhausted, but pattern is. Failure.
return false;
}
//如果path数组未被耗尽,并且是不要求全匹配的,当前pattern元素为**,那么直接返回true,用于匹配spring/config/**/a/b/c vs spring/config/haha/xixi
else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
// Path start definitely matches due to "**" part in pattern.
return true;
}
//如果两者都未被耗尽,能够走到这里,那么就一定出现了**,那么从后往前匹配
// up to last '**'
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
String pattDir = pattDirs[pattIdxEnd];
if (pattDir.equals("**")) {
break;
}
if (!matchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
return false;
}
pattIdxEnd--;
pathIdxEnd--;
}
//如果path数组被耗尽,那么判断当前pattern下标对应的元素是否为**,用于匹配 spring/**/[**/**/]springContext-*.xml vs spring/springContext-*.xml
if (pathIdxStart > pathIdxEnd) {
// String is exhausted
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (!pattDirs[i].equals("**")) {
return false;
}
}
return true;
}
//用于匹配spirng/**/**[a/b/c][/**]/springContext-*.xml vs spring/a/b/springContext-*.xml
while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
int patIdxTmp = -1;
for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
if (pattDirs[i].equals("**")) {
patIdxTmp = i;
break;
}
}
if (patIdxTmp == pattIdxStart + 1) {
// '**/**' situation, so skip one
//跳过连续**的情况
pattIdxStart++;
continue;
}
// Find the pattern between padIdxStart & padIdxTmp in str between
// strIdxStart & strIdxEnd
int patLength = (patIdxTmp - pattIdxStart - 1);
int strLength = (pathIdxEnd - pathIdxStart + 1);
int foundIdx = -1;
//匹配spirng/**/**/a/b/c/**/fg/**/springContext-*.xml vs spring/lala/a/b/c/yiyi/ffg/xixi/springContext-*.xml的情况
strLoop:
for (int i = 0; i <= strLength - patLength; i++) {
for (int j = 0; j < patLength; j++) {
String subPat = pattDirs[pattIdxStart + j + 1];
String subStr = pathDirs[pathIdxStart + i + j];
if (!matchStrings(subPat, subStr, uriTemplateVariables)) {
continue strLoop;
}
}
foundIdx = pathIdxStart + i;
break;
}
//如果没有匹配到相同的片段,那么就返回false
if (foundIdx == -1) {
return false;
}
pattIdxStart = patIdxTmp;
pathIdxStart = foundIdx + patLength;
}
//用于匹配spirng/**/**[a/b/c][/**]/springContext-*.xml vs spring/a/b/springContext-*.xml
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (!pattDirs[i].equals("**")) {
return false;
}
}
return true;
}
AntPathMatcher的路径匹配方式考虑的情况比较多,其中可能比较复杂的地方是标注为strLoop循环标签的地方,这段循环主要是通过锁定a,b,c与lala,a,b,c,yiyi,ffg,xixi片段进行匹配是否有连续的a,b,c fg片段与yiyi,ffg,xixi片段进行匹配。如果都能匹配,那么就到最后会判断fg片段后到上次匹配结束的地方是否全是**,或者说pattIdxStart大于patIdxend(也就是没有**),那么就表示匹配。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?