注:spring version 4.2.0.RELEASE
?
首先,spring 管理注解bean容器主要是:
后者是相当于在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)}; } } }
?这里分类几种情况:
在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操作。
?
?
?
?
?
spring源码阅读一:spring初始化容器时扫描package的过程
原文:http://mutoudotjava.iteye.com/blog/2238921