目录
资源加载
AbstractApplication 实现了ResourceLoader和ResourcePatternResolver。ResourceLoader是加载单个资源的。而时候我们的资源路径可能是以”classpath*“ (代表加载其他jar包资源)开头的,也有可能是带有通配符的,这两种是加载多个资源,这种是通过ResourcePatternResolver加载,ResourcePatternResolver的实现类其实是PathMatchingResourcePatternResolver,它通过递归并使用AntMathMatcher这个工具对模式和找到的路径进行匹配,然后将匹配到的各个资源的路径委托给其成员变量ResourceLoader resourceLoader = DefaultResourceLoader()进行加载。
AntPathMatcher
Spring 在加载我们配置的文件模式符的时候,会将模式符按照 / 分割,然后与我们工程的路径进行匹配如
将 "/aa/bb/cc/dd/ee/ff/1.txt" 分割为 {aa, bb, cc, dd, ee, ff, 1.txt}
? 匹配一个字符
* 匹配0个或多个字符
** 匹配 0 个或多个目录
可以待正则匹配的变量 如 {path1:[a-z]+} 变量名为 path1 匹配 正则式[a-z]+
public class AntPathMatcherTest {
public static void test1() {
AntPathMatcher antPathMatcher = new AntPathMatcher();
// 精准匹配
boolean ret = antPathMatcher.match("/aa/bb/cc/dd/ee/ff/1.txt", "/aa/bb/cc/dd/ee/ff/1.txt");
System.out.println(ret);
// 使用 ? 匹配
ret = antPathMatcher.match("/a?/bb/cc/dd/ee/ff/1.txt", "/aa/bb/cc/dd/ee/ff/1.txt");
System.out.println(ret);
// 使用 * 匹配
ret = antPathMatcher.match("/*/bb/cc/dd/ee/ff/1.txt", "/aa/bb/cc/dd/ee/ff/1.txt");
System.out.println(ret);
// 使用 ** 匹配
ret = antPathMatcher.match("/**/bb/**/dd/ee/ff/1.txt", "/aa/bb/cc/dd/ee/ff/1.txt");
System.out.println(ret);
// 使用 ** 匹配
ret = antPathMatcher.match("/**/bb/**/dd/ee/ff/1.txt", "/aa/bb/cc/dd/ee/ff/1.txt");
System.out.println(ret);
// 模式提取
Map<String, String> map = antPathMatcher.extractUriTemplateVariables("/aa/bb/{path1}/{path2}/ee/ff/1.txt"
, "/aa/bb/cc/dd/ee/ff/1.txt");
System.out.println(map);
map = antPathMatcher.extractUriTemplateVariables("/aa/bb/{path1:\\w+}/{path2:\\d+}/ee/ff/1.txt"
, "/aa/bb/cc/12/ee/ff/1.txt");
System.out.println(map);
}
final static Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{[^/]+?\\}");
public static void test2() {
// path == aa, patter = "a?" => "a."
// path == aa, patter = "a+" => "a.+"
String pattern = "{path1:dd}";
Matcher matcher = GLOB_PATTERN.matcher(pattern);
StringBuilder patternBuilder = new StringBuilder();
List<String> variableNames = new ArrayList<>();
int end = 0;
while (matcher.find()) {
// System.out.println("在[" + matcher.start() + ", " + matcher.end()
// + ")的位置匹配到" + matcher.group())
String match = matcher.group();
patternBuilder.append(quote(pattern, end, matcher.start()));
if ("?".equals(match)) {
patternBuilder.append(".");
}
if ("*".equals(match)) {
patternBuilder.append(".*");
}
if (match.startsWith("{") && match.endsWith("}")) {
int index = match.indexOf(":");
if (index < 0) {
variableNames.add(match.substring(1, match.length() - 1));
patternBuilder.append("(.*)");
} else {
variableNames.add(match.substring(1, index));
patternBuilder.append("(");
patternBuilder.append(match.substring(index + 1, match.length() -1));
patternBuilder.append(")");
}
}
end = matcher.end();
}
patternBuilder.append(quote(pattern, end, pattern.length()));
String theRegPattern = patternBuilder.toString();
System.out.println("使用正则替换了原来的路径统配符之后的结果为" + theRegPattern);
Pattern reg = Pattern.compile(theRegPattern);
String target = "dd";
Matcher matcher1 = reg.matcher(target);
if (matcher1.matches()) {
System.out.println(pattern + " 匹配到了 " + target);
for (int i = 0; i < matcher1.groupCount(); i++) {
String name = variableNames.get(i);
String value = matcher1.group(i + 1);
System.out.println(String.format(("name = %s, value = %s"), name, value));
}
} else {
System.out.println(pattern + " 无法匹配 " + target);
}
}
public static void main(String[] args) {
test2();
}
public static String quote(String s, int start, int end) {
if (start == end) {
return "";
}
return Pattern.quote(s.substring(start, end));
}
}
ResourceLoader
DefaulResourceLoader
ResourceLoader的最终实现是DefaulResourceLoader:
org.springframework.core.io.DefaultResourceLoader#getResource的代码
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)) {
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 (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
org.springframework.core.io.DefaultResourceLoader#getClassLoader
public ClassLoader getClassLoader() {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
org.springframework.util.ClassUtils#getDefaultClassLoader
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
cl = Thread.currentThread().getContextClassLoader();
}
catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
cl = ClassUtils.class.getClassLoader();
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try {
cl = ClassLoader.getSystemClassLoader();
}
catch (Throwable ex) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
}
}
}
return cl;
}
ResourcePatternResolver
其实现类是PathMatchingResourcePatternResolver,案例:
public class ResourcePatternResolverTest {
public static void main(String[] args) throws IOException {
String path = ResourceLoaderTest.class.getName().replace(".", "/");
String dirs = path.substring(0, path.length() - ResourceLoaderTest.class.getSimpleName().length());
// 意图去匹配 当前目录下 所有类名带 Resource 的 class文件
String pattern = "classpath:" + dirs + "/*Resource*.class";
// ResourcePatternResolver 可以通过模式匹配加载多个符合模式的文件, 这种匹配就是通过AntPathMatcher完成的,其单个文件加载是通过ResourceLoader完成的
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(new DefaultResourceLoader());
Resource[] resources = resourcePatternResolver.getResources(pattern);
for (Resource resource : resources) {
if (resource.exists()) {
System.out.println(resource.getURL());
}
}
}
}
org.springframework.core.io.support.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)) {
// 检测其是否带有 * ? { } 判断是否需要用antMathMatcher进行模式匹配
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// 用antMathMatcher进行模式匹配
return findPathMatchingResources(locationPattern);
}
else {
// 直接通过路径进行查找资源
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
// Generally only look for a pattern after a prefix here,
// and on Tomcat only after the "*/" separator for its "war:" protocol.
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
locationPattern.indexOf(':') + 1);
// 去掉前缀后再判断
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// 如果是模式匹配的使用模式匹配
return findPathMatchingResources(locationPattern);
}
else {
// 直接交给 resourceLoader进行单个资源加载
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
他有三个走向:
findPathMatchingResources 按照模式匹配进行解析,它是一个递归的过程
findAllClassPathResources 按照全路径解析
new Resource[] {getResourceLoader().getResource(locationPattern)} 单个资源解析