深度解析kfyty725/loveqq-framework的资源定位:ResourcePatternResolver实现
引言:资源定位的核心挑战
你是否还在为框架中的资源定位逻辑感到困惑?是否曾因路径匹配不准确导致配置文件加载失败?本文将深入剖析loveqq-framework中ResourcePatternResolver接口的实现原理,带你彻底掌握资源定位的核心机制。读完本文后,你将能够:
- 理解
PathMatchingResourcePatternResolver的工作原理 - 掌握Ant风格路径匹配的实现细节
- 学会在实际项目中正确使用资源定位功能
- 解决复杂场景下的资源查找问题
1. ResourcePatternResolver概述
1.1 什么是ResourcePatternResolver
ResourcePatternResolver(资源模式解析器)是loveqq-framework中负责资源定位的核心组件,它能够根据指定的模式匹配规则,在类路径下查找符合条件的资源。该组件广泛应用于框架的各个模块,如MyBatis集成、动态SQL解析、配置文件加载等场景。
1.2 核心实现类:PathMatchingResourcePatternResolver
在loveqq-framework中,PathMatchingResourcePatternResolver是ResourcePatternResolver接口的主要实现类,它提供了基于Ant风格路径模式的资源查找功能。
@Component
@RequiredArgsConstructor
@SuppressWarnings("UrlHashCode")
public class PathMatchingResourcePatternResolver {
private volatile boolean loaded;
private final Set<URL> urls;
private final PatternMatcher patternMatcher;
// 构造函数
public PathMatchingResourcePatternResolver() {
this(new HashSet<>());
}
public PathMatchingResourcePatternResolver(Set<URL> urls) {
this(urls, new AntPathMatcher());
}
// 核心方法
public Set<URL> findResources(String pattern) {
// 实现细节后续分析
}
}
1.3 类关系图
2. PathMatchingResourcePatternResolver实现原理
2.1 初始化过程
PathMatchingResourcePatternResolver的初始化过程主要涉及类路径的解析和资源的预加载:
protected Set<URL> obtainURL() {
if (!this.loaded) {
synchronized (this) {
if (!this.loaded) {
this.urls.addAll(ClassLoaderUtil.resolveClassPath(classLoader(this.getClass())));
this.loaded = true;
}
}
}
return this.urls;
}
上述代码采用了双重检查锁定(Double-Checked Locking)机制,确保类路径的解析只执行一次,提高了资源查找的效率。
2.2 资源查找流程
findResources方法是资源查找的入口,它根据资源的存储位置(JAR包或文件系统)调用不同的查找逻辑:
public Set<URL> findResources(String pattern) {
try {
Set<URL> urls = this.obtainURL();
Set<URL> resources = new HashSet<>();
for (URL url : urls) {
if (url.getFile().endsWith(".jar")) {
resources.addAll(this.findResourcesByJar(new JarFile(url.getFile().replace("%20", " ")), pattern));
} else {
resources.addAll(this.findResourcesByFile(url, pattern));
}
}
return resources;
} catch (IOException e) {
throw ExceptionUtil.wrap(e);
}
}
2.3 JAR包内资源查找
findResourcesByJar方法负责查找JAR包内的资源:
public Set<URL> findResourcesByJar(JarFile jarFile, String pattern) {
Set<URL> resources = new HashSet<>();
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
if (this.patternMatcher.matches(pattern, jarEntry.getName())) {
resources.add(IOUtil.newNestedJarURL(jarFile, jarEntry.getName()));
}
}
return resources;
}
2.4 文件系统资源查找
findResourcesByFile方法负责查找文件系统中的资源:
public Set<URL> findResourcesByFile(URL url, String pattern) {
try {
Set<URL> resources = new HashSet<>();
File[] files = new File(url.getPath()).listFiles();
if (files == null || files.length < 1) {
return resources;
}
for (File file : files) {
if (file.isDirectory()) {
resources.addAll(this.findResourcesByFile(file.toURI().toURL(), pattern));
continue;
}
String filePath = file.getPath();
if (filePath.contains("classes")) {
filePath = filePath.substring(filePath.indexOf("classes" + File.separator) + 8).replace('\\', '/');
}
if (this.patternMatcher.matches(pattern, filePath)) {
resources.add(file.toURI().toURL());
}
}
return resources;
} catch (MalformedURLException e) {
throw ExceptionUtil.wrap(e);
}
}
3. Ant风格路径匹配详解
3.1 PatternMatcher接口
PatternMatcher(模式匹配器)是路径匹配的核心接口,定义了模式匹配的基本方法:
public interface PatternMatcher {
/**
* 路径匹配
*
* @param pattern 规则,eg: /aa/**
* @param source 要匹配的字符串,eg: /aa/bb
* @return true if matched
*/
boolean matches(String pattern, String source);
}
3.2 AntPathMatcher实现
AntPathMatcher是PatternMatcher接口的主要实现类,它支持Ant风格的路径匹配,提供了强大的模式匹配能力。
3.2.1 Ant风格路径规则
Ant风格路径匹配支持以下通配符:
?:匹配一个字符*:匹配零个或多个字符**:匹配零个或多个目录
一些常见的例子:
| 模式 | 说明 |
|---|---|
com/t?st.jsp | 匹配com/test.jsp、com/tast.jsp等 |
com/*.jsp | 匹配com目录下所有.jsp文件 |
com/**/test.jsp | 匹配com路径下所有test.jsp文件 |
org/**/servlet/bla.jsp | 匹配org/servlet/bla.jsp、org/test/servlet/bla.jsp等 |
3.2.2 核心匹配算法
AntPathMatcher的核心是doMatch方法,它实现了复杂的路径匹配逻辑:
protected boolean doMatch(String pattern, String path, boolean fullMatch) {
// 路径分隔符一致性检查
if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
return false;
}
// 分割路径为目录数组
String[] pattDirs = tokenizeToStringArray(pattern, this.pathSeparator, true, true);
String[] pathDirs = tokenizeToStringArray(path, this.pathSeparator, true, true);
int pattIdxStart = 0;
int pattIdxEnd = pattDirs.length - 1;
int pathIdxStart = 0;
int pathIdxEnd = pathDirs.length - 1;
// 匹配第一个**之前的部分
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
String patDir = pattDirs[pattIdxStart];
if ("**".equals(patDir)) {
break;
}
if (!matchStrings(patDir, pathDirs[pathIdxStart])) {
return false;
}
pattIdxStart++;
pathIdxStart++;
}
// 处理剩余部分(包含**的情况)
// ... 省略复杂的匹配逻辑 ...
return true;
}
3.2.3 字符串匹配
matchStrings方法实现了字符串级别的通配符匹配:
private boolean matchStrings(String pattern, String str) {
char[] patArr = pattern.toCharArray();
char[] strArr = str.toCharArray();
int patIdxStart = 0;
int patIdxEnd = patArr.length - 1;
int strIdxStart = 0;
int strIdxEnd = strArr.length - 1;
char ch;
boolean containsStar = false;
for (char aPatArr : patArr) {
if (aPatArr == '*') {
containsStar = true;
break;
}
}
// 没有*号的情况,直接比较
if (!containsStar) {
if (patIdxEnd != strIdxEnd) {
return false; // 长度不同
}
for (int i = 0; i <= patIdxEnd; i++) {
ch = patArr[i];
if (ch != '?') {
if (ch != strArr[i]) {
return false; // 字符不匹配
}
}
}
return true;
}
// 处理包含*号的情况
// ... 省略复杂的匹配逻辑 ...
return true;
}
4. 实际应用场景
4.1 MyBatis集成中的应用
在loveqq-boot-starter-mybatis模块中,PathMatchingResourcePatternResolver被用于扫描Mapper接口和SQL映射文件:
public class MapperScanner {
private PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver;
public void afterPropertiesSet() {
// 扫描Mapper接口
List<Class<?>> collect = Arrays.stream(mapperScan.value())
.flatMap(e -> PackageUtil.scanClass(e, this.pathMatchingResourcePatternResolver).stream())
.collect(Collectors.toList());
// ...
}
}
4.2 动态SQL解析
在loveqq-data-korm模块中,PathMatchingResourcePatternResolver用于加载动态SQL文件:
public class AbstractDynamicProvider {
public String provideSql(Method method) {
// 获取SQL资源路径
String path = this.getSqlPath(method);
PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver =
this.configuration.getPathMatchingResourcePatternResolver();
try {
for (URL file : pathMatchingResourcePatternResolver.findResources(path)) {
// 解析SQL文件内容
return IOUtil.readString(file.openStream());
}
} catch (IOException e) {
throw new ORMException("Load dynamic sql failed: " + path, e);
}
throw new ORMException("Dynamic sql not found: " + path);
}
}
4.3 资源查找流程
5. 性能优化与最佳实践
5.1 避免过度使用**通配符
**通配符会导致递归扫描整个目录结构,可能影响性能。在实际使用中,应尽量指定具体的目录层级。
5.2 资源缓存
PathMatchingResourcePatternResolver已经实现了类路径URL的缓存机制,避免重复解析:
protected Set<URL> obtainURL() {
if (!this.loaded) {
synchronized (this) {
if (!this.loaded) {
this.urls.addAll(ClassLoaderUtil.resolveClassPath(classLoader(this.getClass())));
this.loaded = true;
}
}
}
return this.urls;
}
5.3 最佳实践
1.** 明确资源位置 :尽量指定明确的资源路径,减少通配符的使用 2. 利用缓存 :对于频繁使用的资源模式,考虑缓存查找结果 3. 避免重复扫描 :在应用启动时预加载所需资源,避免运行时重复扫描 4. 合理组织资源 **:按照功能模块组织资源文件,便于查找和维护
6. 总结与展望
6.1 主要知识点回顾
PathMatchingResourcePatternResolver是loveqq-framework中资源定位的核心组件- 基于Ant风格的路径匹配支持灵活的资源查找
AntPathMatcher实现了复杂的路径匹配算法,支持?、*、**等通配符- 资源查找支持JAR包内资源和文件系统资源
6.2 未来优化方向
1.** 并行扫描 :引入并行处理机制,提高多资源同时查找的效率 2. 更丰富的匹配模式 :支持正则表达式等更多匹配模式 3. 资源变更监听 :增加资源变更监听功能,支持热加载 4. 缓存策略优化 **:实现更智能的缓存淘汰策略,平衡内存占用和查找效率
通过本文的深入分析,相信你已经对loveqq-framework中的资源定位机制有了全面的了解。在实际开发中,合理利用PathMatchingResourcePatternResolver可以极大提高资源管理的效率和灵活性。如果你在使用过程中遇到任何问题,欢迎在项目仓库提交issue,我们将及时响应和解答。
附录:常用API参考
PathMatchingResourcePatternResolver
| 方法 | 说明 |
|---|---|
Set<URL> findResources(String pattern) | 根据模式查找资源 |
Set<URL> findResourcesByJar(JarFile jarFile, String pattern) | 查找JAR包内资源 |
Set<URL> findResourcesByFile(URL url, String pattern) | 查找文件系统资源 |
AntPathMatcher
| 方法 | 说明 |
|---|---|
boolean matches(String pattern, String source) | 判断路径是否匹配模式 |
boolean matchStart(String pattern, String path) | 判断路径是否匹配模式前缀 |
String extractPathWithinPattern(String pattern, String path) | 提取模式匹配的路径部分 |
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



