1. 程式人生 > >spring 啟動載入過程

spring 啟動載入過程

spring啟動component-scan類掃描載入過程—原始碼分析
spring通過DispatcherServlet載入:
系統配置:
<servlet>
    <servlet-name>spring</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>


servlet的規範中,如果load-on-startup被設定了,那麼就會被初始化的時候裝載,而servlet裝載時會呼叫其init()方法,那麼自然是呼叫DispatcherServlet的init方法,
通過原始碼一看,竟然沒有,但是並不帶表真的沒有,你會發現在父類的父類中:org.springframework.web.servlet.HttpServletBean有這個方法,


原始碼如下所示:
public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}
		// Set bean properties from init parameters.
		try {
			PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
			initBeanWrapper(bw);
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
			logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			throw ex;
		}
		// Let subclasses do whatever initialization they like.
		initServletBean();
		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}



注意程式碼:initServletBean(); 其餘的都和載入bean關係並不是特別大,跟蹤進去會發現這個方法是在類:org.springframework.web.servlet.FrameworkServlet類中(是DispatcherServlet的父類、HttpServletBean的子類),
內部通過呼叫initWebApplicationContext()來初始化一個WebApplicationContext,原始碼片段
        protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
		if (this.logger.isInfoEnabled()) {
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
		}
		long startTime = System.currentTimeMillis();


		try {
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}
		catch (RuntimeException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}


		if (this.logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - startTime;
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
					elapsedTime + " ms");
		}
	}
接下來需要知道的是如何初始化這個context的(按照使用習慣,其實只要得到了ApplicationContext,就得到了bean的資訊,所以在初始化ApplicationCotext的時候,就已經初始化好了bean的資訊,至少至少,它初始化好了bean的路徑,以及描述資訊),所以我們一旦知道ApplicationCotext是怎麼初始化的,就基本知道bean是如何載入的了。


程式碼片段如下:
protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;


		...
		
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			wac = createWebApplicationContext(rootContext);
		}


		if (!this.refreshEventReceived) {
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			onRefresh(wac);
		}


		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
						"' as ServletContext attribute with name [" + attrName + "]");
			}
		}


		return wac;
	}
因為Root的ApplicationContext的資訊還根本沒建立,所以主要是看createWebApplicationContext這個方法,進去後,該方法前面部分,都是在設定一些相關的引數,
例如我們需要將WEB容器、以及容器的配置資訊設定進去,然後會呼叫一個refresh()方法,這個方法表面上是用來重新整理的,其實也是用來做初始化bean用的,也就是配置修改後,
如果你能呼叫它的這個方法,就可以重新裝載spring的資訊,我們看看原始碼中的片段如下:
        protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
		
		...
		
		wac.setServletContext(getServletContext());
		wac.setServletConfig(getServletConfig());
		wac.setNamespace(getNamespace());
		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));


		...


		postProcessWebApplicationContext(wac);
		applyInitializers(wac);
		wac.refresh();
	}

其實這個方法,不論是通過ClassPathXmlApplicationContext還是WEB裝載都會呼叫這裡,我們看下ClassPathXmlApplicationContext中呼叫的部分:
        public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
			throws BeansException {
		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			refresh();
		}
	}
他們的區別在於,web容器中,用servlet裝載了,servlet中包裝了一個XmlWebApplicationContext而已,而ClassPathXmlApplicationContext是直接呼叫的,他們共同點是,
不論是XmlWebApplicationContext、還是ClassPathXmlApplicationContext都繼承了類(間接繼承),AbstractApplicationContext,這個類中的refresh()方法是共用的,
也就是他們都呼叫的這個方法來載入bean的,在這個方法中,通過obtainFreshBeanFactory方法來構造beanFactory的
public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();


			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();


			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);
			
				...
				
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);


				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);


				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);


				// Initialize message source for this context.
				initMessageSource();


				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();


				// Initialize other special beans in specific context subclasses.
				onRefresh();


				// Check for listener beans and register them.
				registerListeners();


				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);


				// Last step: publish corresponding event.
				finishRefresh();
			}
			
			...
		}
	}

是不是看到一層呼叫一層很煩人,其實回過頭來想一想,它沒一層都有自己的處理動作,畢竟spring不是簡單的做一個bean載入,即使是這樣,
我們最少也需要做xml解析、類裝載和例項化的過程,每個步驟可能都有很多需求,因此分離設計,使得程式碼更加具有擴充套件性,我們繼續來看obtainFreshBeanFactory方法的描述:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		refreshBeanFactory();
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (logger.isDebugEnabled()) {
			logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
		}
		return beanFactory;
	}
這裡很多人可能會不太注意refreshBeanFactory()這個方法,尤其是第一遍看這個程式碼的,如果你忽略掉,你可能會找不到bean在哪裡載入的,前面提到了refresh其實可以用以初始化,
這裡也是這樣,refreshBeanFactory如果沒有初始化beanFactory就是初始化它了,後面你看到的都是getBeanFactory的程式碼,也就是已經初始化好了,這個refreshBeanFactory方法類AbstractRefreshableApplicationContext中的方法,
它是AbstractApplicationContext的子類,同樣不論是XmlWebApplicationContext、還是ClassPathXmlApplicationContext都繼承了它,因此都能呼叫到這個一樣的初始化方法,來看看body部分的程式碼
    protected final void refreshBeanFactory() throws BeansException {
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}
建立了一個beanFactory,然後下面的方法可以通過名稱就能看出是“載入bean的定義”,將beanFactory傳入,自然要載入到beanFactory中了,createBeanFactory就是例項化一個beanFactory沒別的,
我們要看的是bean在哪裡載入的,現在貌似還沒看到重點,繼續跟蹤loadBeanDefinitions(DefaultListableBeanFactory)方法

它由AbstractXmlApplicationContext類中的方法實現,web專案中將會由類:XmlWebApplicationContext來實現,其實差不多,主要是看啟動檔案是在那裡而已,
如果在非web類專案中沒有自定義的XmlApplicationContext,那麼其實功能可以參考XmlWebApplicationContext,可以認為是一樣的功能。那麼看看loadBeanDefinitions方法如下:
        protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);


		// Configure the bean definition reader with this context's
		// resource loading environment.
		beanDefinitionReader.setEnvironment(getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));


		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}


	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			for (String configLocation : configLocations) {
				reader.loadBeanDefinitions(configLocation);
			}
		}
	}
這裡有一個XmlBeanDefineitionReader,是讀取XML中spring的相關資訊(也就是解析SpringContext.xml的),這裡通過getConfigLocations()獲取到的就是這個或多個檔案的路徑,會迴圈,
通過XmlBeanDefineitionReader來解析,跟蹤到loadBeanDefinitions方法裡面,會發現方法實現體在XmlBeanDefineitionReader的父類:AbstractBeanDefinitionReader中,程式碼如下:
        public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader == null) {
			throw new BeanDefinitionStoreException(
					"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
		}


		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				int loadCount = loadBeanDefinitions(resources);
				if (actualResources != null) {
					for (Resource resource : resources) {
						actualResources.add(resource);
					}
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
				}
				return loadCount;
			}
			catch (IOException ex) {
				throw new BeanDefinitionStoreException(
						"Could not resolve bean definition resource pattern [" + location + "]", ex);
			}
		}
		else {
			// Can only load single resources by absolute URL.
			Resource resource = resourceLoader.getResource(location);
			int loadCount = loadBeanDefinitions(resource);
			if (actualResources != null) {
				actualResources.add(resource);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
			}
			return loadCount;
		}
	}
這裡大家會疑惑,為啥裡面還有一個loadBeanDefinitions,大家要知道,我們目前只解析到我們的springContext.xml在哪裡,但是還沒解析到springContext.xml的內容是什麼,可能有多個spring的配置檔案,這裡會出現多個Resource,
所以是一個數組(這裡如何通過location找到檔案部分,在我們找class的時候自然明瞭,大家先不糾結這個問題)。

接下來有很多層呼叫,會以此呼叫:
AbstractBeanDefinitionReader.loadBeanDefinitions(Resources []) 迴圈Resource陣列,呼叫方法:
XmlBeanDefinitionReader.loadBeanDefinitions(Resource ) 和上面這個類是父子關係,接下來會做:doLoadBeanDefinitions、registerBeanDefinitions的操作,在註冊beanDefinitions的時候,其實就是要真正開始解析XML了
步驟一:loadBeanDefinitions
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
		Assert.notNull(encodedResource, "EncodedResource must not be null");
		if (logger.isInfoEnabled()) {
			logger.info("Loading XML bean definitions from " + encodedResource.getResource());
		}


		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null) {
			currentResources = new HashSet<EncodedResource>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		try {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}


步驟二:doLoadBeanDefinitions
        protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
			Document doc = doLoadDocument(inputSource, resource);
			return registerBeanDefinitions(doc, resource);
		}
		catch (BeanDefinitionStoreException ex) {
			throw ex;
		}
		catch (SAXParseException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
		}
		catch (SAXException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"XML document from " + resource + " is invalid", ex);
		}
		catch (ParserConfigurationException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Parser configuration exception parsing XML from " + resource, ex);
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"IOException parsing XML document from " + resource, ex);
		}
		catch (Throwable ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Unexpected exception parsing XML document from " + resource, ex);
		}
	}



    步驟三:registerBeanDefinitions
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		int countBefore = getRegistry().getBeanDefinitionCount();
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}


	步驟三呼叫了DefaultBeanDefinitionDocumentReader類的registerBeanDefinitions方法,如下圖所示:
	protected void doRegisterBeanDefinitions(Element root) {
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);


		if (this.delegate.isDefaultNamespace(root)) {
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
					if (logger.isInfoEnabled()) {
						logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
								"] not matching: " + getReaderContext().getResource());
					}
					return;
				}
			}
		}
		preProcessXml(root);
		parseBeanDefinitions(root, this.delegate);
		postProcessXml(root);
		this.delegate = parent;
	}



中間有解析XML的過程,但是貌似我們不是很關心,我們就關係類是怎麼載入的,雖然已經到XML解析部分了,所以主要看parseBeanDefinitions這個方法,
裡面會呼叫到BeanDefinitionParserDelegate類的parseCustomElement方法,用來解析bean的資訊:
        protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		if (delegate.isDefaultNamespace(root)) {
			NodeList nl = root.getChildNodes();
			for (int i = 0; i < nl.getLength(); i++) {
				Node node = nl.item(i);
				if (node instanceof Element) {
					Element ele = (Element) node;
					if (delegate.isDefaultNamespace(ele)) {
						parseDefaultElement(ele, delegate);
					}
					else {
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}

這裡解析了XML的資訊,跟蹤進去,會發現用了NamespaceHandlerSupport的parse方法,它會根據節點的型別,找到一種合適的解析BeanDefinitionParser(介面),
他們預先被spring註冊好了,放在一個HashMap中,例如我們在spring 的annotation掃描中,通常會配置:<context:component-scan base-package="com.xxx" />

此時根據名稱“component-scan”就會找到對應的解析器來解析,而與之對應的就是ComponentScanBeanDefinitionParser的parse方法,這地方已經很明顯有掃描bean的概念在裡面了,
這裡的parse獲取到後,中間有一個非常非常關鍵的步驟那就是定義了ClassPathBeanDefinitionScanner來掃描類的資訊,它掃描的是什麼?是載入的類還是class檔案呢?
答案是後者,為何,因為有些類在初始化化時根本還沒被載入,ClassLoader根本還沒載入,只是ClassLoader可以找到這些class的路徑而已
        public BeanDefinition parse(Element element, ParserContext parserContext) {
		String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
		basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
		String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
				ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);


		// Actually scan for bean definitions and register them.
		ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
		Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
		registerComponents(parserContext.getReaderContext(), beanDefinitions, element);


		return null;
	}
注意這裡的scanner建立後,最關鍵的是doScan的功能,解析XML我想來看這個的不是問題,如果還不熟悉可以先看看,那麼我們得到了類似:com.xxx這樣的資訊,就要開始掃描類的列表,
那麼再哪裡掃描呢?這裡的doScan返回了一個Set<BeanDefinitionHolder>我們感到希望就在不遠處,進去看看doScan方法。
        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) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}

我們看到這麼大一坨程式碼,其實我們目前不關心的程式碼,暫時可以不管,我們就看怎麼掃描出來的,可以看出最關鍵的掃描程式碼是:findCandidateComponents(String basePackage)方法,
也就是通過每個basePackage去找到有那些類是匹配的,我們這裡假如配置了com.abc,或配置了 * 兩種情況說明。
    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) {
				if (traceEnabled) {
					logger.trace("Scanning " + resource);
				}
				if (resource.isReadable()) {
					try {
						MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
						if (isCandidateComponent(metadataReader)) {
							ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
							sbd.setResource(resource);
							sbd.setSource(resource);
							if (isCandidateComponent(sbd)) {
								if (debugEnabled) {
									logger.debug("Identified candidate component class: " + resource);
								}
								candidates.add(sbd);
							}
							else {
								if (debugEnabled) {
									logger.debug("Ignored because not a concrete top-level class: " + resource);
								}
							}
						}
						else {
							if (traceEnabled) {
								logger.trace("Ignored because not matching any filter: " + resource);
							}
						}
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to read candidate component class: " + resource, ex);
					}
				}
				else {
					if (traceEnabled) {
						logger.trace("Ignored because not readable: " + resource);
					}
				}
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
		}
		return candidates;
	}
是已經拿到了類的定義,會組裝資訊,如果我們配置了 com.abc會組裝為:classpath*:com/abc/**/*.class ,如果配置是 * ,那麼將會被組裝為classpath*:*/**/*.class ,
但是這個好像和我們用的東西不太一樣,java中也沒見這種URL可以獲取到,spring到底是怎麼搞的呢?就要看如下程式碼
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
它竟然神奇般的通過這個路徑獲取到了URL,你一旦跟蹤你會發現,獲取出來的全是.class的路徑,包括jar包中的相關class路徑,
這裡有些細節,我們先不說,先看下這個resourcePatternResolover是什麼型別的,看到定義部分是:
        private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); 
做了一個測試,用一個簡單main方法寫了一段:
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();  
	Resource[] resources = resourcePatternResolver.getResources("classpath*:com/abc/**/*.class");
獲取出來的果然是那樣,可以猜測,這個和ClassLoader的getResource方法有關係了,因為太類似了,我們跟蹤進去看下:
        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_ALL_URL_PREFIX就是字串 classpath*: , 我們傳遞引數進來的時候一致,繼續走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);
			URL rootDirURL = rootDirResource.getURL();
			if (equinoxResolveMethod != null) {
				if (rootDirURL.getProtocol().startsWith("bundle")) {
					rootDirURL = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirURL);
					rootDirResource = new UrlResource(rootDirURL);
				}
			}
			if (rootDirURL.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
				result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirURL, subPattern, getPathMatcher()));
			}
			else if (ResourceUtils.isJarURL(rootDirURL) || isJarResource(rootDirResource)) {
				result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirURL, 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()]);
	}
這裡有一個rootDirPath,這個地方有個容易出錯的,是如果你配置的是 com.abc,那麼rootDirPath部分應該是:classpath*:com/abc/ 而如果配置是 * 那麼classpath*: 只有這個結果,
而不是classpath*:*(這裡我就不說擷取字串的原始碼了),回到上一段程式碼,這裡再次呼叫了getResources(String)方法,又回到前面一個方法,這一次,依然是以classpath*:開頭,
所以第一層 if 語句會進去,而第二層不會,為什麼?在裡面的isPattern() 的實現中是這樣寫的:
public boolean isPattern(String path) {  
		return (path.indexOf('*') != -1 || path.indexOf('?') != -1);  
	}  
在匹配前,做了一個substring的操作,會將“classpath*:”這個字串去掉,如果是配置的是com.abc就變成了"com/abc/",而如果配置為*,那麼得到的就是“” ,也就是長度為0的字串,
因此在我們的這條路上,這個方法返回的是false,就會走到程式碼段findAllClassPathResources中,這就是為什麼上面提到會有用途的原因,好了,最最最最關鍵的地方來了哦。例如我們知道了一個com/abc/為字首,
此時要知道相關的classpath下面有哪些class是匹配的,如何做?自然用ClassLoader,我們看看Spring是不是這樣做的:
        protected Resource[] findAllClassPathResources(String location) throws IOException {
		String path = location;
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		Set<Resource> result = doFindAllClassPathResources(path);
		if (logger.isDebugEnabled()) {
			logger.debug("Resolved classpath location [" + location + "] to resources " + result);
		}
		return result.toArray(new Resource[result.size()]);
	}
果然不出所料,它也是用ClassLoader,只是它自己提供的getClassLoader()方法,也就是和spring的類使用同一個載入器範圍內的,以保證可以識別到一樣的classpath,自己模擬的時候,可以用一個類

類名.class.getClassLoader().getResources("");
如果放為空,那麼就是獲取classpath的相關的根路徑(classpath可能有很多,但是根路徑,可以被合併),也就是如果你配置的*,獲取到的將是這個,也許你在web專案中,你會獲取到專案的根路徑(classes下面,以及tomcat的lib目錄)。
如果寫入一個:com/abc/ 那麼得到的將是掃描相關classpath下面所有的class和jar包中與之匹配的類名(字首部分)的路徑資訊,但是需要注意的是,如果有兩層jar包,而你想要掃描的類或者說想要通過spring載入的類在第二層jar包中,
這個方法是獲取不到的,這不是spring沒有去做這個事情,而是,java提供的getResources方法就是這樣的,有朋友問我的時候,正好遇到了類似的事情,另外需要注意的是,getResources這個方法是包含當前路徑的一個遞迴檔案查詢
(一般環境變數中都會配置 . ),所以如果是一個jar包,你要執行的話,切記放在某個根目錄來跑,因為當前目錄,就是根目錄也會被遞迴下去,你的程式會被莫名奇怪地慢。
回到上面的程式碼中,在findPathMatchingResources中我們這裡剛剛獲取到base的路徑列表,也就是所有包含類似com/abc/為字首的路徑,或classpath合併後的目錄根路徑;此時我們需要下面所有的class,
那麼就需要的是遞迴,這裡我就不再跟蹤了,大家可以自己去跟蹤裡面的幾個方法呼叫:doFindPathMatchingJarResources、doFindPathMatchingFileResources 。
幾乎不會用到:VfsResourceMatchingDelegate.findMatchingResources,所以主要是上面兩個,分別是jar包中的和工程裡面的class,跟蹤進去會發現,程式碼會不斷遞迴迴圈呼叫目錄路徑下的class檔案的路徑資訊,
最終會拿到相關的class列表資訊,但是這些class還並沒有做檢測是否有annotation,那是下一步做的事情,但是下一個步驟已經很簡單了,因為要檢測一個類的annotation
這裡大家還可以通過以下簡單的方式來測試呼叫路徑的問題:

ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true);  
Set<BeanDefinition> beanDefinitions = provider.findCandidateComponents("com/abc");  
for(BeanDefinition beanDefinition : beanDefinitions) {  
    System.out.println(beanDefinition.getBeanClassName()   
                    + "\t" + beanDefinition.getResourceDescription()  
                    + "\t" + beanDefinition.getClass());  
} 

回到findCandidateComponents方法中,isCandidateComponent(MetadataReader metadataReader)方法,這個方法裡先迴圈excludeFilters,再迴圈includeFilters,excludeFilters預設情況下沒有啥內容,
includeFilters預設情況下最少會有一個new AnnotationTypeFilter(Component.class);也就是預設情況下excludeFilters排除內容不會迴圈,includeFilters包含內容最少會匹配到AnnotationTypeFilter,
呼叫AnnotationTypeFilter.match方法是其父類AbstractTypeHierarchyTraversingFilter.math()方法,其內部呼叫matchSelf()調回子類的AnnotationTypeFilter.matchSelf()方法。
該方法中用||連線兩個判定分別是hasAnnotation、hasMetaAnnotation,前者判定註解名稱本身是否匹配因此Component肯定能匹配上,後者會判定註解的meta註解是否包含,Service、Controller、Repository註解都註解了Component,
因此它們會在後者匹配上。這樣match就肯定成立了
        protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
		for (TypeFilter tf : this.excludeFilters) {
			if (tf.match(metadataReader, this.metadataReaderFactory)) {
				return false;
			}
		}
		for (TypeFilter tf : this.includeFilters) {
			if (tf.match(metadataReader, this.metadataReaderFactory)) {
				return isConditionMatch(metadataReader);
			}
		}
		return false;
	}	

看了這麼多,是不是有點暈,沒關係,誰第一回看都這樣,當你下一次看的時候,有個思路就好了,我這裡並沒有像UML一樣理出他們的層次關係,和呼叫關係,僅僅針對程式碼呼叫逐層來說明,
大家如果初步看就是,由Servlet初始化來建立ApplicationContext,在設定了Servelt相關引數後,獲取servlet的配置檔案路徑或自己指定的配置檔案路徑(applicationContext.xml或其他的名字,可以一個或多個),
然後通過系列的XML解析,以及針對每種不同的節點型別使用不同的載入方式,其中component-scan用於指定掃描類的對應有一個Scanner,它會通過ClassLoader的getResources方法來獲取到class的路徑資訊,
那麼class的路徑都能獲取到,類的什麼還拿不到呢?
step 4下面 看refresh()的核心finishBeanFactoryInitialization(beanFactory);
經過obtainFreshBeanFactory() 這個方法,我們的beanFactory就準備好了,接下來我們主要圍繞finishBeanFactoryInitialization(beanFactory)方法,聊聊Spring是如何例項化bean的。
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
		// Initialize conversion service for this context.
		if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
				beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
			beanFactory.setConversionService(
					beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
		}


		// Register a default embedded value resolver if no bean post-processor
		// (such as a PropertyPlaceholderConfigurer bean) registered any before:
		// at this point, primarily for resolution in annotation attribute values.
		if (!beanFactory.hasEmbeddedValueResolver()) {
			beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
				@Override
				public String resolveStringValue(String strVal) {
					return getEnvironment().resolvePlaceholders(strVal);
				}
			});
		}


		// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
		String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
		for (String weaverAwareName : weaverAwareNames) {
			getBean(weaverAwareName);
		}


		// Stop using the temporary ClassLoader for type matching.
		beanFactory.setTempClassLoader(null);


		// Allow for caching all bean definition metadata, not expecting further changes.
		beanFactory.freezeConfiguration();


		// Instantiate all remaining (non-lazy-init) singletons.
		beanFactory.preInstantiateSingletons();
	}

這個方法,就是為了例項化非懶載入的單例bean,我們走進 beanFactory.preInstantiateSingletons(); 看一看
(注意,這裡例項化單例,而Struts中Action是每次請求都建立,所以Action並不是單例的)

public void preInstantiateSingletons() throws BeansException {
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Pre-instantiating singletons in " + this);
		}


		// Iterate over a copy to allow for init methods which in turn register new bean definitions.
		// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
		List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);


		// Trigger initialization of all non-lazy singleton beans...
		for (String beanName : beanNames) {
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				if (isFactoryBean(beanName)) {
					final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
					boolean isEagerInit;
					if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
						isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
							@Override
							public Boolean run() {
								return ((SmartFactoryBean<?>) factory).isEagerInit();
							}
						}, getAccessControlContext());
					}
					else {
						isEagerInit = (factory instanceof SmartFactoryBean &&
								((SmartFactoryBean<?>) factory).isEagerInit());
					}
					if (isEagerInit) {
						getBean(beanName);
					}
				}
				else {
					getBean(beanName);
				}
			}
		}


		// Trigger post-initialization callback for all applicable beans...
		for (String beanName : beanNames) {
			Object singletonInstance = getSingleton(beanName);
			if (singletonInstance instanceof SmartInitializingSingleton) {
				final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
				if (System.getSecurityManager() != null) {
					AccessController.doPrivileged(new PrivilegedAction<Object>() {
						@Override
						public Object run() {
							smartSingleton.afterSingletonsInstantiated();
							return null;
						}
					}, getAccessControlContext());
				}
				else {
					smartSingleton.afterSingletonsInstantiated();
				}
			}
		}
	}	



因為Struts專案中Action並不滿足條件 “不是抽象類, 且是單例, 且不是延遲載入”,所以該方法對我們自定義的Action幾乎沒有用,我們一直迴圈直到單例的物件出現,再來看這個程式碼。


我們把這小段程式碼提出來單獨看

for (String beanName : beanNames) { //將載入進來的beanDefinitionNames迴圈分析
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { //如果不是抽象類, 且是單例, 且不是延遲載入
				if (isFactoryBean(beanName)) { //是否實現FactoryBean介面
					final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName); 
					boolean isEagerInit;
					if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
						isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
							@Override
							public Boolean run() {
								return ((SmartFactoryBean<?>) factory).isEagerInit();
							}
						}, getAccessControlContext());
					}
					else {
						isEagerInit = (factory instanceof SmartFactoryBean &&
								((SmartFactoryBean<?>) factory).isEagerInit());
					}
					if (isEagerInit) {
						getBean(beanName);
					}
				}
				else {
					getBean(beanName);
				}
			}
}

判斷這個bean是否是抽象類,是否是單例,是否延遲載入
如果不是抽象類, 且是單例, 且不是延遲載入,那麼判斷是否實現 FactoryBean 介面
如果實現了 FactoryBean,則 getBean(FACTORY_BEAN_PREFIX + beanName),否則 getBean(beanName)

如果我們跟進 getBean 這個方法,發現它呼叫了 doGetBean 這個方法,我們再跟進,這個方法非常長(這裡就不貼出來了)


在這個方法中,你可以不斷地去跟進(這裡不再做具體展開),你會發現大概的步驟差不多是
建立一個bean的例項
將這個例項封裝到BeanWrapper中
















而這裡bean的例項化方法,其實是 beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
這個instantiate 方法在 package org.springframework.beans.factory.support; --> SimpleInstantiationStrategy.java
在這之中採用反射機制將物件進行了例項化。


其實還涉及到bean例項化以後,Spring是如何將bean的屬性進行注入的,這裡暫時不做進一步的展開了。


可以知道的是,最終屬性的注入是利用反射機制,通過setter賦值的