本篇文章是由朋友的一篇博客引出的,博客原文地址:http://jinnianshilongnian.iteye.com/blog/1416322
他这篇博客比较细的讲解了classpath与classpath*,以及通配符的使用,那些配置能成功加载到资源,那些配置加载不了资源。但是我相信仍然有很多同学不明白,为什么是这样的,知其然,不知其所以然,那么本篇文章将慢慢为你揭开神秘的面纱,让你知其然,更知其所以然。
关于Spring Resource的资源类型以及继承体系我们已经在上一篇文件粗略的说了一下。Spring加载Resource文件是通过ResourceLoader来进行的,那么我们就先来看看ResourceLoader的继承体系,让我们对这个模块有一个比较系统的认知。
上图仅右边的继承体系,仅画至AbstractApplicationContext,由于ApplicationContext的继承体系,我们已经在前面章节给出,所以为了避免不必要的复杂性,本章继承体系就不引入ApplicationContext。
我们还是来关注本章的重点————classpath 与 classpath*以及通配符是怎么处理的
首先,我们来看下ResourceLoader的源码
public interface ResourceLoader { /** Pseudo URL prefix for loading from the class path: "classpath:" */ String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; Resource getResource(String location); ClassLoader getClassLoader(); }
我们发现,其实ResourceLoader接口只提供了classpath前缀的支持。而classpath*的前缀支持是在它的子接口ResourcePatternResolver中。
public interface ResourcePatternResolver extends ResourceLoader { /** * Pseudo URL prefix for all matching resources from the class path: "classpath*:" * This differs from ResourceLoader's classpath URL prefix in that it * retrieves all matching resources for a given name (e.g. "/beans.xml"), * for example in the root of all deployed JAR files. * @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX */ String CLASSPATH_ALL_URL_PREFIX = "classpath*:"; Resource[] getResources(String locationPattern) throws IOException; }
通过2个接口的源码对比,我们发现ResourceLoader提供 classpath下单资源文件的载入,而ResourcePatternResolver提供了多资源文件的载入。
ResourcePatternResolver有一个实现类:PathMatchingResourcePatternResolver,那我们直奔主题,查看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)) { //是否包含?或者* 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)}; } } }
由此我们可以看出在加载配置文件时,以是否是以classpath*开头分为2大类处理场景,每大类在又根据路径中是否包括通配符分为2小类进行处理,
处理的流程图如下:
从上图看,整个加载资源的场景有三条处理流程
- 以classpath*开头,但路径不包含通配符的
protected Resource[] findAllClassPathResources(String location) throws IOException { String path = location; if (path.startsWith("/")) { path = path.substring(1); } Enumeration<URL> resourceUrls = getClassLoader().getResources(path); Set<Resource> result = new LinkedHashSet<Resource>(16); while (resourceUrls.hasMoreElements()) { URL url = resourceUrls.nextElement(); result.add(convertClassLoaderURL(url)); } return result.toArray(new Resource[result.size()]); }我们可以看到,最关键的一句代码是:Enumeration<URL> resourceUrls = getClassLoader().getResources(path);
public ClassLoader getClassLoader() { return getResourceLoader().getClassLoader(); } public ResourceLoader getResourceLoader() { return this.resourceLoader; } //默认情况下 public PathMatchingResourcePatternResolver() { this.resourceLoader = new DefaultResourceLoader(); }其实上面这3个方法不是最关键的,之所以贴出来,是让大家清楚整个调用链,其实这种情况最关键的代码在于ClassLoader的getResources()方法。那么我们同样跟进去,看看源码
public Enumeration<URL> getResources(String name) throws IOException { Enumeration[] tmp = new Enumeration[2]; if (parent != null) { tmp[0] = parent.getResources(name); } else { tmp[0] = getBootstrapResources(name); } tmp[1] = findResources(name); return new CompoundEnumeration(tmp); }是不是一目了然了?当前类加载器,如果存在父加载器,则向上迭代获取资源, 因此能加到jar包里面的资源文件。
- 不以classpath*开头,且路径不包含通配符的
return new Resource[] {getResourceLoader().getResource(locationPattern)};上面我们已经贴过getResourceLoader()的逻辑了, 即默认是DefaultResourceLoader(),那我们进去看看getResouce()的实现
public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); 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 new UrlResource(url); } catch (MalformedURLException ex) { // No URL -> resolve as resource path. return getResourceByPath(location); } } }其实很简单,如果以classpath开头,则创建为一个ClassPathResource,否则则试图以URL的方式加载资源,创建一个UrlResource.
- 路径包含通配符的
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException { //拿到能确定的目录,即拿到不包括通配符的能确定的路径 比如classpath*:/aaa/bbb/spring-*.xml 则返回classpath*:/aaa/bbb/ //如果是classpath*:/aaa/*/spring-*.xml,则返回 classpath*:/aaa/ String rootDirPath = determineRootDir(locationPattern); //得到spring-*.xml String subPattern = locationPattern.substring(rootDirPath.length()); //递归加载所有的根目录资源,要注意的是递归的时候又得考虑classpath,与classpath*的情况,而且还得考虑根路径中是否又包含通配符,参考上面那张流程图 Resource[] rootDirResources = getResources(rootDirPath); Set<Resource> result = new LinkedHashSet<Resource>(16); //将根目录所有资源中所有匹配我们需要的资源(如spring-*)加载result中 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()]); }值得注解一下的是determineRootDir()方法的作用,是确定根目录,这个根目录必须是一个能确定的路径,不会包含通配符。如果classpath*:aa/bb*/spring-*.xml,得到的将是classpath*:aa/ 可以看下他的源码
protected String determineRootDir(String location) { int prefixEnd = location.indexOf(":") + 1; int rootDirEnd = location.length(); while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) { rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1; } if (rootDirEnd == 0) { rootDirEnd = prefixEnd; } return location.substring(0, rootDirEnd); }分析到这,结合测试我们可以总结一下:
相关推荐
spring-beans:Bean工厂与装配 spring-context:上下文,即IOC容器 spring-context-support:对IOC的扩展,以及IOC子容器 spring-context-indexer:类管理组件和Classpath扫描 spring-expression:表达式语句 切面编程: ...
2.1.2 MVC模式的处理过程 2.2 Model规范 2.2.1 Model1规范 2.2.2 Model2规范 2.3 使用MVC的优劣 2.3.1 使用MVC模式的好处 2.3.2 使用MVC模式的不足之处 2.4 目前市场上常见的轻量级J2EE开发容器 2.5 小结 第二篇 ...
2.1.2 MVC模式的处理过程 2.2 Model规范 2.2.1 Model1规范 2.2.2 Model2规范 2.3 使用MVC的优劣 2.3.1 使用MVC模式的好处 2.3.2 使用MVC模式的不足之处 2.4 目前市场上常见的轻量级J2EE开发容器 2.5 小结 第二篇 ...
2.1.2 MVC模式的处理过程 2.2 Model规范 2.2.1 Model1规范 2.2.2 Model2规范 2.3 使用MVC的优劣 2.3.1 使用MVC模式的好处 2.3.2 使用MVC模式的不足之处 2.4 目前市场上常见的轻量级J2EE开发容器 2.5 小结 第二篇 ...
# 商城项目开发过程 ## 处理登录注册功能 ### 1. 分析项目 当需要开发某个项目时,首先,应该分析这个项目中,需要处理哪些种类的数据!例如:用户、商品、商品类别、收藏、订单、购物车、收货地址…… 然后,将...
2.1.4 编译程序(javac)与classpath 30 .2.2 管理原始码与位码文档 31 2.2.1 编译程序(javac)与sourcepath 31 2.2.2 使用package管理类 33 2.2.3 使用import偷懒 36 2.3 使用ide 38 2.3.1 ide项目管理...
关于 这是Java编译器插件的前端Web应用程序。 技术领域 Spring Boot 2.1.4 胸腺3.0.3 MySQL 8.0 码头工人18.09 Docker组成3 Neo4j 4.2.3 引导程序4.3.1 jQuery 3.2.0 特征 Java代码分析和问题检测 查询创建和...
2.1.2 MVC模式的处理过程 2.2 Model规范 2.2.1 Model1规范 2.2.2 Model2规范 2.3 使用MVC的优劣 2.3.1 使用MVC模式的好处 2.3.2 使用MVC模式的不足之处 2.4 目前市场上常见的轻量级J2EE开发容器 2.5 小结 第二篇 ...
P6spy是一个JDBC Driver的包装工具,p6spy通过对JDBC Driver的封装以达到对SQL语句的监听和分析,以达到各种目的。 p6spy的安装步骤: 1. 下载p6spy的安装包 2. 把p6spy的jar包放到Classpath中,如果是WEB App...
通过上述对功能的定制,我们可以看到在应用中我们对sosoo的编程接口并不多,而且目前系统都是基于set的方式注入aop注入对象,这样很容易和spring等基于set方式的依赖注入(IOC)框架集成。 1.Roboter类,spider...
JBPM与Spring的集成开发(包括可 ... 入门级的东西. JBPM文档1、 加入JBPM支持包下载jbpm-starters-kit-3.1.4在项目的classPath中加入jbpm-3.1.4.jar、jbpm-identity-3.1.2.jar、jbpm-webapp-3.1.2.jar,jbpm-3.1.4....
学习过程中的心态一定要保持专一,网上关于语言间的“PK”到处都是,别被浮躁影响!认准了Java,你就坚持!克服心魔,恒心最终会给你回报的。 Java的体系分为Java SE、Java EE和Java ME(JDK 5.0以前分别叫J2SE、J2EE和...
│ 第67节:分析如何使用Memcached开发.avi │ 第68节:Memcached结合业务功能开发.avi │ 第69节:Nginx+Varnish+基本业务功能+Memcached.avi │ 第70节:应用Memcached后的体系结构.avi │ 第71节:ActiveMQ入门和...