PathMatchingResourcePatternResolver
该实现类是本文的重中之重。
它是基于模式匹配的,默认使用org.springframework.util.AntPathMatcher进行路径匹配,它除了支持ResourceLoader支持的前缀外,还额外支持classpath*:用于加载所有匹配的类路径Resource
public class PathMatchingResourcePatternResolver implements ResourcePatternResolver { // 内部持有一个resourceLoader的引用 private final ResourceLoader resourceLoader; // 我们发现,它内部使用是AntPathMatcher进行匹配的(Spring内部AntPathMatcher是PathMatcher接口的唯一实现。 //如果你想改变此匹配规则,你可以自己实现(当然这是完全没必要的)) private PathMatcher pathMatcher = new AntPathMatcher(); // 默认使用的DefaultResourceLoader 当然也可以指定 public PathMatchingResourcePatternResolver() { this.resourceLoader = new DefaultResourceLoader(); } public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) { Assert.notNull(resourceLoader, "ResourceLoader must not be null"); this.resourceLoader = resourceLoader; } public PathMatchingResourcePatternResolver(@Nullable ClassLoader classLoader) { this.resourceLoader = new DefaultResourceLoader(classLoader); } ... // 显然,我们也可以自己指定解析器,默认使用AntPathMatcher public void setPathMatcher(PathMatcher pathMatcher) { Assert.notNull(pathMatcher, "PathMatcher must not be null"); this.pathMatcher = pathMatcher; } // 最终是委托给ResourceLoader去做了 @Override public Resource getResource(String location) { return getResourceLoader().getResource(location); } // 这个是核心方法~~~~~~~ @Override public Resource[] getResources(String locationPattern) throws IOException { Assert.notNull(locationPattern, "Location pattern must not be null"); // 以`classpath*:`打头~~~ if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { // 把前最后面那部分截取出来,看看是否是模版(包含*和?符号都属于模版,否则不是) if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) { // a class path resource pattern // 事patter,那就交给这个方法,这个是个核心方法 这里传入的是locationPattern return findPathMatchingResources(locationPattern); } else { // all class path resources with the given name // 如果不是pattern,那就完全匹配。去找所有的path下的匹配上的就成~~~ return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length())); } } // 不是以`classpath*:`打头的~~~~ else { // 支持到tomcat的war:打头的方式~~~ int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 : locationPattern.indexOf(':') + 1); if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) { return findPathMatchingResources(locationPattern); } // 如果啥都不打头,那就当作一个正常的处理,委托给ResourceLoader直接去处理 else { // a single resource with the given name return new Resource[] {getResourceLoader().getResource(locationPattern)}; } } } // 根据Pattern去匹配资源~~~~ protected Resource[] findPathMatchingResources(String locationPattern) throws IOException { // 定位到跟文件夹地址。比如locationPattern=classpath:META-INF/spring.factories 得到classpath*:META-INF/ // 若是classpath*:META-INF/ABC/*.factories 得到的就是 classpath*:META-INF/ABC/ // 简单的说,就是截取第一个不是patter的地方的前半部分 String rootDirPath = determineRootDir(locationPattern); // 后半部分 这里比如就是:*.factories String subPattern = locationPattern.substring(rootDirPath.length()); // 这个递归就厉害了,继续调用了getResources("classpath*:META-INF/")方法 // 相当于把该文件夹匹配的所有的资源(注意:可能会比较多的),最后在和patter匹配即可~~~~ // 比如此处:只要jar里面有META-INF目录的 都会被匹配进来~~~~~~ Resource[] rootDirResources = getResources(rootDirPath); Set<Resource> result = new LinkedHashSet<>(16); for (Resource rootDirResource : rootDirResources) { // resolveRootDirResource是留给子类去复写的。但是Spring没有子类复写此方法,默认实现是啥都没做~~~ rootDirResource = resolveRootDirResource(rootDirResource); URL rootDirUrl = rootDirResource.getURL(); // 这个if就一般不看了 是否为了做兼容~~~ if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) { URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl); if (resolvedUrl != null) { rootDirUrl = resolvedUrl; } rootDirResource = new UrlResource(rootDirUrl); } // 支持vfs协议(JBoss)~~~ if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) { result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher())); } // 是否是jar文件或者是jar资源(显然大多数情况下都是此情况~~~) else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) { // 把rootDirUrl, subPattern都交给doFindPathMatchingJarResources去处理 result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern)); } // 不是Jar文件(那就是本工程里字的META-INF目录~~~) // 那就没啥好说的,直接给个subPattern去匹配吧 注意这个方法名是File,上面是jar else { result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern)); } } // 最终转换为数组返回。 注意此处的result是个set,是有去重的效果的~~~ return result.toArray(new Resource[0]); } protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, URL rootDirURL, String subPattern) { ... // 这个方法就源码不细说了, //Find all resources in jar files that match the given location pattern // 就是去这个jar里面去找所有的资源(默认利用Ant风格匹配~) //此处用到了`java.util.jar.JarFile`、`ZipFile`、`java.net.JarURLConnection`等等 // 路径匹配:getPathMatcher().match(subPattern, relativePath) 使用的此方法去校对 } // Find all resources in the file system that match the given location pattern // 简单的说,这个就是在我们自己的项目里找~~~~ protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern) throws IOException { // rootDir:最终是个绝对的路径地址,带盘符的。来代表META-INF这个文件夹~~~ File rootDir; try { rootDir = rootDirResource.getFile().getAbsoluteFile(); } catch (IOException ex) { return Collections.emptySet(); } // FileSystem 最终是根据此绝对路径 去文件系统里找 return doFindMatchingFileSystemResources(rootDir, subPattern); } // 这个比较简单:就是把该文件夹所有的文件都拿出来dir.listFiles(),然后一个个去匹配呗~~~~ // 备注:子类`ServletContextResourcePatternResolver`复写了此方法~~~~~ 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<>(matchingFiles.size()); for (File file : matchingFiles) {] // 最终用FileSystemResource把File包装成一个Resource~ result.add(new FileSystemResource(file)); } return result; } ... }
至此,整个过程核心就是遍历、递归、匹配、路径的处理。说难也难,说容易也就这么些事。可能路径处理方面是比较复杂也是最容易出错的地方
ServletContextResourcePatternResolver
显然它是web下,用于获取资源的。上面已经介绍了web容器下都是使用它去获取资源~~(ServletContextResourceLoader) 它复写了这个方法:
public class ServletContextResourcePatternResolver extends PathMatchingResourcePatternResolver { ... // 一般会使用这个构造函数构造~~~ public ServletContextResourcePatternResolver(ServletContext servletContext) { super(new ServletContextResourceLoader(servletContext)); } @Override protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern) throws IOException { // 如果你这个Resource是web的,那就交给它处理 // doRetrieveMatchingServletContextResources是核心处理方法~~~ if (rootDirResource instanceof ServletContextResource) { ServletContextResource scResource = (ServletContextResource) rootDirResource; ServletContext sc = scResource.getServletContext(); String fullPattern = scResource.getPath() + subPattern; Set<Resource> result = new LinkedHashSet<>(8); doRetrieveMatchingServletContextResources(sc, fullPattern, scResource.getPath(), result); return result; } else { return super.doFindPathMatchingFileResources(rootDirResource, subPattern); } } }
ApplicationContext
ApplicationContext
这个接口就再熟悉不过了,它代表整个Spring应用的上下文。它也继承自ResourcePatternResolver
,赋予了上下文更便捷的处理Resource的能力。
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver { ... }
大部分ApplicationContext的实现类已经在上面都说明了,这里只需要简单的说说WebApplicationContext这个分支~~~
WebApplicationContext
这个是web环境下的容器的父接口。org.springframework.web.context.WebApplicationContext
public interface WebApplicationContext extends ApplicationContext { String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"; String SCOPE_REQUEST = "request"; String SCOPE_SESSION = "session"; String SCOPE_APPLICATION = "application"; String SERVLET_CONTEXT_BEAN_NAME = "servletContext"; String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters"; String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes"; // 这个接口相当于只新增了这个一个接口~~~ @Nullable ServletContext getServletContext(); }
ResourceLoaderAware
这是Spring为了方便我们获得Resource而提供的感知接口~~~
Spring会自动调用实现了ResourceLoaderAware接口类方法:serResourceLoader(),将ApplicationContext的ResourceLoader注入进去,之后对它getResource(),就获取了系统的Resource了,可谓非常的贴心~
总结
applicationContext.getResource()会采用和ApplicationContext相同的策略来访问资源:
- ClassPathXmlApplicationContext 则底层Resource是ClassPathResource实例
- FileSystemXmlApplicationContext 则底层Resource是FileSystemResource实例
- XmlWebApplicationContext 则底层Resource是ServletContextResource实例
所以我们一般这么使用就行,对开发者而言,不用关心具体使用的是那种Resource:
Resource resource = applicationContext.getResource("classpath:spring-mvc.xml"); // 或者 // 即使创建的时候使用的是`classpath:`,但是最后getResource()实际上使用的还是FileSystemResource 这点需要注意~~~ applicationContext=new FileSystemXmlApplicationContext("classpath:bean.xml"); applicationContext.getResource("book.xml");