springIOC源码接口分析(六):ResourceLoader
参考博客: https://www.cnblogs.com/jixp/articles/10702486.html
一 定义方法
Spring提供了ResourceLoader接口用于实现不同的Resource加载策略,即将不同Resource实例的创建交给ResourceLoader来计算.
接口提供了两个方法和一个字符串常量:
/** class path: "classpath:". */ String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; /** * 通过提供的资源location参数获取Resource实例,该实例可以是ClasPathResource、FileSystemResource、UrlResource等, * 但是该方法返回的Resource实例并不保证该Resource一定是存在的,需要调用exists方法判断 */ Resource getResource(String location); /** * 此方法将ClassLoader暴露出来,可以直接调用getClassLoader()方法获得ClassLoader,而不是依赖于Thread Context ClassLoader, * 因为有些时候ResourceLoader内部使用自定义的ClassLoader */ @Nullable ClassLoader getClassLoader();
二 ResourcePatternResolver
在实际开发中经常会遇到需要通过某种匹配方式查找资源,比如通配符,而且可能有多个资源匹配这种模式,在Spring中提供了ResourcePatternResolver接口用于实现这种需求并实现了ResourceLoader接口:
接口定义了一个方法和一个正则字符串常量:
/** * 用于查找匹配classpath下所有的匹配Resource */ String CLASSPATH_ALL_URL_PREFIX = "classpath*:"; /** * 用于根据传入的locationPattern查找和其匹配的Resource实例,并以数组的形式返回,在返回的数组中不可以存在 * 相同的Resource实例 */ Resource[] getResources(String locationPattern) throws IOException;
三 实现类
ResourceLoader接口的实现类在spring中有DefaultResourceLoader、FileSystemResourceLoader和ServletContextResourceLoader等.
ResourcePatternResolver接口的实现类有PathMatchingResourcePatternResolver。并且ApplicationContext接口也继承了ResourcePatternResolver,
在实现中,ApplicationContext的实现类会将逻辑代理给相关的单独实现类,如PathMatchingResourcePatternResolver等。在ApplicationContext中ResourceLoaderAware接口,可以将ResourceLoader(自身)注入到实现该接口的Bean中,
在Bean中可以将其强制转换成ResourcePatternResolver接口使用(为了安全,强转前需要判断)。
1 DefaultResourceLoader
DefaultResourceLoader是ResourceLoader的默认实现,AbstractApplicationContext继承该类。主要是实现了ResourceLoader的getResource()方法:
@Override public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); /** * protocolResolvers:是类的成员变量,ProtocolResolver类的Set集合 * ProtocolResolver是解析location的自定义拓展类接口,只定义了resolve解析方法,根据传入的location字符串,解析出对应的Resource资源 * 我们可以自定义一个类实现ProtocolResolver接口,然后实现该resolve方法,就可以解析特定的location得到Resoure。 * 这一步主要是看是否有自定义的协议解析可以解析出Resource对象,如果有就返回这个实例 */ for (ProtocolResolver protocolResolver : this.protocolResolvers) { Resource resource = protocolResolver.resolve(location, this); if (resource != null) { return resource; } } //返回ClassPathContextResource实例 if (location.startsWith("/")) { return getResourceByPath(location); } /** * 如果是 classpath开头,返回ClassPathResource实例 * 开发中一般在这返回配置文件实例,比如 new ClassPathXmlApplicationContext("classpath:spring.xml"); */ else if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { /** * 尝试创建UrlResource,如果当前location没有定义URL的协议(即以”file:”、”zip:”等开头,比如使用相对路径”resources/META-INF/MENIFEST.MF), * 则创建UrlResource会抛出MalformedURLException, * 此时调用getResourceByPath()方法获取Resource实例 */ 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); } } }
2 FileSystemResourceLoader
FileSystemResourceLoader继承了DefaultResourceLoader,覆写了getResourceByPath()方法,
DefaultResourceLoader的getResourceByPath方法返回的是ClassPathContextResource实例,而返回的是FileSystemContextResource实例.
3 ServletContextResourceLoader
ServletContextResourceLoader类也继承自DefaultResourceLoader,和FileSystemResourceLoader一样,它的getResource方法的实现逻辑和DefaultResourceLoader相同,
不同的是它实现了自己的getResourceByPath方法,即当UrlResource创建失败时,它会使用ServletContextResource实例:
4 PathMatchingResourcePatternResolver
PathMatchingResourcePatternResolver类实现了ResourcePatternResolver接口,它包含了对ResourceLoader接口的引用,在对继承自ResourceLoader接口的方法的实现会代理给该引用,
同时在getResources()方法实现中,当找到一个匹配的资源location时,可以使用该引用解析成Resource实例。默认使用DefaultResourceLoader类,用户可以使用构造函数传入
自定义的ResourceLoader。
PathMatchingResourcePatternResolver还包含了一个对PathMatcher接口的引用,该接口基于路径字符串实现匹配处理,如判断一个路径字符串是否包含通配符(’*’、’?’),
判断给定的path是否匹配给定的pattern等。Spring提供了AntPathMatcher对PathMatcher的默认实现,表达该PathMatcher是采用Ant风格的实现。
其中PathMatcher的接口定义如下:
//判断path是否是一个pattern,即判断path是否包含通配符 boolean isPattern(String path); //判断给定path是否可以匹配给定pattern boolean match(String pattern, String path); //判断给定path是否可以匹配给定pattern,该方法不同于match,它只是做部分匹配,即当发现给定path匹配给定path的可能性比较大时,即返回true。 // 可以先使用它确定需要全面搜索的范围,然后在这个比较小的范围内再找出所有的资源文件全路径做匹配运算 boolean matchStart(String pattern, String path); //去除path中和pattern相同的字符串,只保留匹配的字符串。 // 比如如果pattern为”/doc/csv/*.htm”,而path为”/doc/csv/commit.htm”,则该方法的返回值为commit.htm。 //该方法默认pattern和path已经匹配成功,因而算法比较简单 String extractPathWithinPattern(String pattern, String path);
PathMatchingResourcePatternResolver中getResources方法的实现:
@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()))) { // 查找所有匹配名称的resource return findPathMatchingResources(locationPattern); } else { //查找所有该名称的resource,不同目录里的 return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length())); } } else { //获取截取前缀长度 int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 : locationPattern.indexOf(':') + 1); if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) { // 查找所有匹配名称的resource return findPathMatchingResources(locationPattern); } else { //直接获取一个resource return new Resource[] {getResourceLoader().getResource(locationPattern)}; } } }