`
木头.java
  • 浏览: 6304 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

spring源码阅读一:spring初始化容器时扫描package的过程

阅读更多

注:spring version 4.2.0.RELEASE

 

首先,spring 管理注解bean容器主要是:

  • AnnotationConfigApplicationContext(org.springframework.context.annotation)
  • AnnotationConfigWebApplicationContext (org.springframework.web.context.support)

后者是相当于在web环境下的AnnotationConfigApplicationContext(This is essentially the equivalent of AnnotationConfigApplicationContext for a web environment.)

所以主要看一下AnnotationConfigApplicationContext。

这个类中扫描的工作主要是在

 

public void scan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		this.scanner.scan(basePackages);
	}

 这里scanner对应的类是ClassPathBeanDefinitionScanner,它和它的父类ClassPathScanningCandidateComponentProvider共同完成扫描packages的任务,也是本次阅读的重点

具体来看:

 

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
			//省略
			}
		}
		return beanDefinitions;
	}

doScan方法启动扫描器扫描指定的package ,其中findCandidateComponents是其父类的方法

 

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
		Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
		try {
			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
					resolveBasePackage(basePackage) + "/" + this.resourcePattern;
			Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
			boolean traceEnabled = logger.isTraceEnabled();
			boolean debugEnabled = logger.isDebugEnabled();
			for (Resource resource : resources) {
                          //省略
		        }
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
		}
		return candidates;
	}

 这时候又冒出来一个resourcePatternResolver,它是spring中本地资源解析器接口的一个实现,人称PathMatchingResourcePatternResolver,这就是用来解析我们写在配置文件里面classpath*:之类的东西,看一下它的getResources方法

 

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

 这里分类几种情况:

  • 以”classpath*:“为前缀并且包含了通配符(?或*),则调用findPathMatchingResources,否则调用findAllClassPathResources
  • 而没有该前缀的情况,如果还是有通配符,同上,否则这是一个单个资源路径,就直接获取。

findAllClassPathResources完成扫描任务的是doFindAllClassPathResources

 

protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
		Set<Resource> result = new LinkedHashSet<Resource>(16);
		ClassLoader cl = getClassLoader();
		Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
		while (resourceUrls.hasMoreElements()) {
			URL url = resourceUrls.nextElement();
			result.add(convertClassLoaderURL(url));
		}
		if ("".equals(path)) {
			// The above result is likely to be incomplete, i.e. only containing file system references.
			// We need to have pointers to each of the jar files on the classpath as well...
			addAllClassLoaderJarRoots(cl, result);
		}
		return result;
	}

 那个ClassLoader就是我们平时用的Thread.currentThread().getContextClassLoader(),spring把它封装到了ClassUtils中,这个类也可以直接拿来当工具类使用。

所以,到了这里,就相当于用classloader去读一边指定路径下的各种资源。

再看一下findPathMatchingResources如何处理用通配符的情况

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 (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
				result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
			}
			else if (isJarResource(rootDirResource)) {
				result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
			}
			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()]);
	}

 首先将路径分解为根目录和子目录,同样使用getResources获得根目录下的所有目录,然后遍历它们,根据不同的类型,根据子目录逐条匹配。暂且不去管那个URL_PROTOCOL_VFS,貌似和JBOSS VFS API有关,之前版本的spring并没有这个。那么在doFindPathMatchingJarResources中主要是使用的

java.util.jar.JarFile去获取jar中的资源,而在doFindPathMatchingFileResources中主要是使用java.io.File

 

扫描package的过程大致就如此了,考虑了各种情况,可以学习到Spring是如何处理java项目中的路径,以及文件IO操作。

 

 

 

 

 

0
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics