本文發表於個人獨立博客:Geeekrjava
最近幾天跟同事聊起Spring的一些問題,對一些地方有些疑問,趁這兩天有點空,看看Spring的源碼,瞭解下具體的實現細節。本文基於Spring 4.0.5版本。node
首先Web項目使用Spring是經過在web.xml裏面配置<br>org.springframework.web.context.ContextLoaderListener
初始化IOC容器的。web
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
那就以此爲切入點順藤摸瓜。spring
public class ContextLoaderListener extends ContextLoader implements ServletContextListener
ContextLoaderListener
繼承了ContextLoader
,而且實現ServletContextListener
接口。當Server容器(通常指tomcat)啓動時,會收到事件初始化。tomcat
@Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); }
initWebApplicationContext
方法是在org.springframework.web.context.ContextLoader
類裏面。方法太長,分段讀一下。app
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { throw new IllegalStateException("Cannot initialize context because there is already a root application context present - " +"check whether you have multiple ContextLoader* definitions in your web.xml!"); } Log logger = LogFactory.getLog(ContextLoader.class); servletContext.log("Initializing Spring root WebApplicationContext"); if (logger.isInfoEnabled()) { logger.info("Root WebApplicationContext: initialization started"); } long startTime = System.currentTimeMillis();
首先是判斷servletContext
中是否已經註冊了WebApplicationContext
,若是有則拋出異常,避免重複註冊。而後就是啓用log,啓動計時。本方法的關鍵就在於try代碼塊裏的內容ide
try { // Store context in local instance variable, to guarantee that // it is available on ServletContext shutdown. if (this.context == null) { this.context = createWebApplicationContext(servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; if (!cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> // determine parent for root web application context, if any. ApplicationContext parent = loadParentContext(servletContext); cwac.setParent(parent); } configureAndRefreshWebApplicationContext(cwac, servletContext); } } servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { currentContext = this.context; } else if (ccl != null) { currentContextPerThread.put(ccl, this.context); } if (logger.isDebugEnabled()) { logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); } if (logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); } return this.context; }
這裏面有幾個關鍵的方法。首先看一下createWebApplicationContext()
post
protected WebApplicationContext createWebApplicationContext(ServletContext sc) { Class<?> contextClass = determineContextClass(sc); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); } return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); }
首先determineContextClass()
方法查明具體的Context
類,他會讀取servletContext
的初始化參數contextClass
,此參數咱們一半不配置,因此Spring
就會讀取跟org.springframework.web.context.WebApplicationContext
同一個包下面的ContextLoader.properties
文件讀取默認設置,反射出org.springframework.web.context.support.XmlWebApplicationContext
類來。接下來就是在configureAndRefreshWebApplicationContext()
方法裏將新建立的XmlWebApplicationContext
進行初始化。首先會設置一個默認ID,即org.springframework.web.context.WebApplicationContext:
+你項目的ContextPath
。學習
if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // The application context id is still set to its original default // value // -> assign a more useful id based on available information String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); if (idParam != null) { wac.setId(idParam); } else { // Generate default id... wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath())); } }
緊接着就是將ServletContext
設置成XmlWebApplicationContext
的屬性,這樣Spring
就能在上下文裏輕鬆拿到ServletContext
了。ui
wac.setServletContext(sc);
接下來就是讀取web.xml
文件中的contextConfigLocation
參數。若是沒有配置就會去讀WEB-INF下的applicationContext.xml
文件。
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:beans.xml</param-value> </context-param>
並將值設置(就是咱們的Spring配置文件的路徑)進XmlWebApplicationContext
中。而後就會在指定的路徑加載配置文件。
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (configLocationParam != null) { wac.setConfigLocation(configLocationParam); }
接下來就是customizeContext(sc, wac)
方法,此方法會根據用戶配置的globalInitializerClasses
參數來初始化一些用戶自定義的屬性,通常咱們不配置,因此這裏什麼也不作。
最後登場的就是最核心的方法了,
wac.refresh();
在這個方法裏,會完成資源文件的加載、配置文件解析、Bean定義的註冊、組件的初始化等核心工做,咱們一探究竟。
@Override 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); try { // 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(); } catch (BeansException ex) { // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } } }
次方法是同步的,避免重複刷新,每一個步驟都放在單獨的方法內,流程清晰,是值得學習的地方。這裏面有個重要的方法是finishBeanFactoryInitialization(beanFactory);
,裏面的內容是Spring如何實例化bean,並注入依賴的,這個內容下一節講,本節只說明Spring是如何加載class文件的。
首先就是prepareRefresh()
方法。
protected void prepareRefresh() { this.startupDate = System.currentTimeMillis(); synchronized (this.activeMonitor) { this.active = true; } if (logger.isInfoEnabled()) { logger.info("Refreshing " + this); } // Initialize any placeholder property sources in the context environment initPropertySources(); // Validate that all properties marked as required are resolvable // see ConfigurablePropertyResolver#setRequiredProperties getEnvironment().validateRequiredProperties(); }
此方法作一些準備工做,如記錄開始時間,輸出日誌,initPropertySources();
和getEnvironment().validateRequiredProperties();
通常沒幹什麼事。
接下來就是初始化BeanFactory
,是整個refresh()
方法的核心,其中完成了配置文件的加載、解析、註冊
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
看看它裏面都作了些什麼?
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); } return beanFactory; }
首先refreshBeanFactory()
:
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); } }
咱們看到會建立一個DefaultListableBeanFactory
實例
DefaultListableBeanFactory beanFactory = createBeanFactory();
再設置一個ID
beanFactory.setSerializationId(getId());
而後設置一些自定義參數:
customizeBeanFactory(beanFactory);
這裏面最重要的就是loadBeanDefinitions(beanFactory);
方法了。
@Override 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(this.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); }
此方法會經過XmlBeanDefinitionReader
加載bean定義。具體的實現方法是在org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions
方法中定義的。這裏設計了層層調用,有好多重載方法,主要就是加載Spring全部的配置文件(可能會有多個),以備後面解析,註冊之用。我一路追蹤到org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(Element root)
protected void doRegisterBeanDefinitions(Element root) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { Assert.state(this.environment != null, "Environment must be set for evaluating profiles"); String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!this.environment.acceptsProfiles(specifiedProfiles)) { return; } } BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(this.readerContext, root, parent); preProcessXml(root); parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate = parent; }
這裏建立了一個BeanDefinitionParserDelegate
示例,解析XML的過程就是委託它完成的,咱們不關心它是怎樣解析XML的,咱們只關心是怎麼加載類的,因此就要看parseBeanDefinitions(root, this.delegate)
方法了。
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元素的是delegate.parseCustomElement(ele)
方法,最終會走到一下方法.
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { String namespaceUri = getNamespaceURI(ele); NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
這裏會根據不一樣的XML節點,會委託NamespaceHandlerSupport
找出合適的BeanDefinitionParser
,若是咱們配置了
<context:component-scan base-package="com.geeekr.service,com.geeekr.dao" />
那麼對應BeanDefinitionParser
就是org.springframework.context.annotation.ComponentScanBeanDefinitionParser
,來看看它的parse
方法。
@Override public BeanDefinition parse(Element element, ParserContext parserContext) { String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE), 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; }
不難看出這裏定義了一個ClassPathBeanDefinitionScanner
,經過它去掃描包中的類文件,***注意:這裏是類文件而不是類,由於如今這些類尚未被加載,只是ClassLoader能找到這些class的路徑而已。***到目前爲止,感受真想距離咱們愈來愈近了。順着繼續往下摸。進入doSacn
方法裏,映入眼簾的又是一大坨代碼,可是咱們只關心觀點的部分。
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; }
一眼就能看出是經過
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
有時候不得不佩服這些外國人起名字的功力,把掃描出來的類叫作candidates(候選人);真是不服不行啊,這種名字真的很容易理解有不有?哈哈,貌似扯遠了。繼續往下看。這裏只列出方法的主題部分。
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);
先看這兩句:
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + "/" + this.resourcePattern;
假設咱們配置的須要掃描的包名爲com.geeekr.service
,那麼packageSearchPath
的值就是classpath*:com.geeekr.service/**/*.class
,意思就是com.geeekr.service包(包括子包)下全部class文件;若是配置的是*
,那麼packageSearchPath
的值就是classpath*:*/**/*.class
。這裏的表達式是Spring本身定義的。Spring會根據這種表達式找出相關的class文件。
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
這一句就把相關class文件加載出來了,那咱們就要看看,Spring到底是如何把class文件找到的了。首先看看resourcePatternResolver
的定義:
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
進入getResources
方法
@Override 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*:
開頭。前面咱們看到Spring已經給咱們添加了這個頭,這裏固然符合條件了。接着會進入findPathMatchingResources
方法。在這裏又把**/*.class
去掉了,而後在調用getResources
方法,而後在進入findAllClassPathResources
方法。這裏的參數只剩下包名了例如com/geeekr/service/
。
protected Resource[] findAllClassPathResources(String location) throws IOException { String path = location; if (path.startsWith("/")) { path = path.substring(1); } ClassLoader cl = getClassLoader(); Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(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()]); }
真相大白了,Spring也是用的ClassLoader
加載的class文件。一路追蹤,原始的ClassLoader是Thread.currentThread().getContextClassLoader();
。到此爲止,就拿到class文件了。 Spring會將class信息封裝成BeanDefinition
,而後再放進DefaultListableBeanFactory
的beanDefinitionMap
中。
拿到了class文件後,就要看看Spring是如何裝配bean的了,下一節,繼續看。