spring如何解析通配符路径

本文介绍如何使用Spring的PathMatchingResourcePatternResolver类实现根据通配符路径加载XML文件。通过简单的示例代码展示如何加载多个XML文件,并深入分析了Spring源码中资源定位和匹配的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在spring的配置文件中、经常看见类似这样的配置路径:
classpath:/com/module/**/*sql.xml

系统会根据配置路径自动加载符合路径规则的xml文件
假如让你实现这样的功能:
[b]根据一个通配符路径加载符合规则的xml文件你会怎么做? [/b]

先看一个小例子:

import java.io.IOException;

import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

public class XMLManager {

private String location = "classpath:*.xml";

public void readFile() throws IOException {
ResourcePatternResolver resourceLoader = new PathMatchingResourcePatternResolver();
Resource[] source = resourceLoader.getResources(location);
for (int i = 0; i < source.length; i++) {
Resource resource = source[i];
System.out.println(resource.getFilename());

}
}

public static void main(String[] args) {

XMLManager m = new XMLManager();
try {
m.readFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}


输出结果:

applicationContext.xml
logback.xml
menu_my.xml

是不是很简单?
只要调用PathMatchingResourcePatternResolver的getResources方法就可以实现我们想要的功能。
下面深入研究一下spring的源码PathMatchingResourcePatternResolver:
[i][b]1、getResources[/b][/i]方法

public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// a class path resource (multiple resources for same name possible)
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)};
}
}
}

该方法主要是判断locationPattern是否包含有通配符、如果包含通配符则调用findPathMatchingResources方法、没有包含通配符就不需要解析了
[i][b]2、findPathMatchingResources[/b][/i]方法

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);
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()]);
}

把locationPattern拆分成两部分:rootDirPath 和subPattern
rootDirPath是根目录路径
subPattern是子目录路径匹配规则字符串
历遍根目录下的所有子目录、并得到所有的子目录
在doFindPathMatchingFileResources(rootDirResource, subPattern)方法中
再根据子目录逐个逐个去匹配subPattern
[i][b]3、doFindPathMatchingFileResources[/b][/i]

protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
throws IOException {

File rootDir;
try {
rootDir = rootDirResource.getFile().getAbsoluteFile();
}
catch (IOException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Cannot search for matching files underneath " + rootDirResource +
" because it does not correspond to a directory in the file system", ex);
}
return Collections.emptySet();
}
return doFindMatchingFileSystemResources(rootDir, subPattern);
}

跳转到另一个方法doFindMatchingFileSystemResources:

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<Resource>(matchingFiles.size());
for (File file : matchingFiles) {
result.add(new FileSystemResource(file));
}
return result;
}

retrieveMatchingFiles


protected Set<File> retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
if (!rootDir.exists()) {
// Silently skip non-existing directories.
if (logger.isDebugEnabled()) {
logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist");
}
return Collections.emptySet();
}
if (!rootDir.isDirectory()) {
// Complain louder if it exists but is no directory.
if (logger.isWarnEnabled()) {
logger.warn("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory");
}
return Collections.emptySet();
}
if (!rootDir.canRead()) {
if (logger.isWarnEnabled()) {
logger.warn("Cannot search for matching files underneath directory [" + rootDir.getAbsolutePath() +
"] because the application is not allowed to read the directory");
}
return Collections.emptySet();
}
String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
if (!pattern.startsWith("/")) {
fullPattern += "/";
}
fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
Set<File> result = new LinkedHashSet<File>(8);
doRetrieveMatchingFiles(fullPattern, rootDir, result);
return result;
}

调用递归方法:
doRetrieveMatchingFiles

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;
}
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);
}
}
if (getPathMatcher().match(fullPattern, currPath)) {
result.add(content);
}
}
}
<think>好的,我现在需要帮用户解决在logging配置中使用通配符设置mapper日志级别的问题。首先,我得回忆一下常见的日志框架,比如Log4j、Logback和Log4j2,它们各自的配置方式可能不同,但通配符的使用可能有相似之处。 用户提到的是“mapper”,通常这可能是指MyBatis的Mapper接口,或者是其他ORM框架中的类似组件。在日志配置中,通常需要针对这些Mapper接口设置特定的日志级别,比如DEBUG或TRACE,以便查看SQL语句的执行情况。 接下来,我需要考虑不同日志框架中如何使用通配符。比如,Log4j2支持使用包名通配符,比如com.example.mapper.*,但具体的语法可能因框架而异。需要确认各个框架是否支持通配符,以及正确的配置格式。 然后,用户的问题可能涉及到具体的配置文件示例,比如logback.xml、log4j2.properties或logging.properties。需要为每种情况提供相应的配置示例,并说明通配符的写法。 同时,要注意不同框架的配置差异。例如,Logback使用<logger>元素,而Log4j2可能有不同的标签。还要提醒用户关于通配符的匹配规则,比如前缀匹配还是中间包是否需要使用**来表示多级包名。 另外,用户可能需要了解如何设置不同的日志级别,比如DEBUG或TRACE,以及这些级别对日志输出的影响。比如,MyBatis的Mapper接口通常需要DEBUG级别来打印SQL,而更详细的日志可能需要TRACE级别。 还需要考虑是否需要添加appender的引用,或者是否需要配置特定的输出格式。此外,可能存在的常见错误,比如通配符使用不正确导致配置不生效,也需要提醒用户检查。 最后,总结一下不同日志框架的配置方法,确保用户能够根据自己的项目情况选择合适的配置方式,并验证配置是否生效。可能还需要建议用户通过重启应用或触发日志输出来测试配置是否正确应用。</think>在日志配置文件中使用通配符设置mapper的日志级别,主要通过包路径模式匹配实现。以下是常见日志框架的配置方法: ### 一、Logback 配置示例(XML格式) ```xml <configuration> <!-- 设置所有mapper接口的日志级别 --> <logger name="com.example.mapper.*" level="DEBUG"/> <!-- 多级包路径使用双星号 --> <logger name="**.dao.**Mapper" level="TRACE"/> <root level="INFO"> <appender-ref ref="CONSOLE"/> </root> </configuration> ``` 通配符说明: - `*` 匹配单层包名 - `**` 匹配任意层级包路径[^1] ### 二、Log4j2 配置示例(XML格式) ```xml <Configuration> <Loggers> <!-- 使用结尾通配符 --> <Logger name="*.mapper" level="DEBUG"/> <!-- 组合模式匹配 --> <Logger name="com.*.service.*Mapper" level="TRACE"/> <Root level="INFO"/> </Loggers> </Configuration> ``` ### 三、Spring Boot 配置(application.properties) ```properties # 使用包路径通配符 logging.level.com.example.dao.*Mapper=DEBUG # 多级包配置 logging.level.org.springframework.*=WARN ``` ### 四、验证配置的注意事项 1. 确保通配符语法与日志框架版本匹配 2. 检查包路径是否完全一致(大小写敏感) 3. 通过`java -Dorg.slf4j.simpleLogger.defaultLogLevel=TRACE`启动参数临时验证 4. 使用`LoggerFactory.getLogger("完整类名")`确认日志器名称 ### 五、通配符优先级规则 1. 精确匹配 > 通配符匹配 2. 更长的模式优先匹配 3. 按配置文件中的声明顺序应用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值