Springソース読解1:springコンテナを初期化するときにpackageをスキャンするプロセス

6949 ワード

注:spring version 4.2.0.RELEASE
 
まずspring管理注記beanコンテナは主に以下のとおりです.
  • AnnotationConfigApplicationContext(org.springframework.context.annotation)
  • AnnotationConfigWebApplicationContext (org.springframework.web.context.support)

  • 後者は、Web環境におけるA n o t i o n t i o n C o f i g ApplicationContext(This is essentially the equivalent of A n o t i o n C o f i g ApplicationContext 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に対応するクラスはC l a s P h T h BeanDefinitionScannerであり、親クラスのC l a s P h T h S c a n n n i n g CandidateComponentProviderとともに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メソッドは、findCandidateComponentsが親のメソッドであるスキャナスキャンで指定されたpackageを起動します.
     
    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;
    	}

    この時また1つのresourcePatternResolverが現れて、それはspringの中でローカルの資源の解析器のインタフェースの1つの実現で、人は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は私たちが普段使っているものです.currentThread().getContextClassLoader()、springはそれをClassUtilsにカプセル化し、このクラスは直接ツールクラスとして使用することもできます.
    したがって,ここまで来るとclassloaderで読みながら経路下の各種リソースを指定することに相当する.
    ワイルドカードの扱い方を見てみましょう
    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を使用してルートディレクトリの下のすべてのディレクトリを取得し、それらを巡り、異なるタイプに応じてサブディレクトリに基づいて1つずつ一致します.そのURLはともかくPROTOCOL_VFSは、JBOSS VFS APIと関係があるようですが、以前のバージョンのspringではこれはありませんでした.ではdofindPathMatchingJarResourcesでは主に
    java.util.jar.JarFileはjarのリソースを取得し、dofindPathMatchingFileResourcesでは主にjavaを使用する.io.File
     
    パッケージをスキャンする過程は大体このようにして、様々な状況を考慮して、Springがjavaプロジェクトのパスをどのように処理するか、ファイルIO操作を学ぶことができます.