【Spring原始碼解析】—— 結合SpringMVC過程理解IOC容器初始化之註解部分探究
前面的文章寫了xml中直接配置bean進行IOC的過程解析,接下來會針對註解進行IOC容器初始化的過程解析
因為會與之前的內容存在部分重疊,因此會針對相同的部分簡略帶過,針對不同的部分做重點說明:
一、Xml的配置和程式碼中的註解配置:
applicationContext.xml配置新增:
<context:component-scan base-package="cn.lx.controller" />
程式碼中配置註解修改:
@Controller public class TestController { @RequestMapping("/test.form") public void execute(){ return ; } }
二、詳解:
入口部分:ContextLoaderListener類中的contextInitialized,進入到ContextLoader類的initWebApplicationContext
方法中,該方法中執行的關鍵方法是:configureAndRefreshWebApplicationContext()進入到ConfigurableWebApplicationContext
類的例項wac.refresh()呼叫中,至此進入到具體接下來的load階段了
load過程:
進入到AbstractApplicationContext類的refresh()類中,之後進入到ObtainFreshBeanFactory()方法中,一路往下跟進到實現方法,之後進入到:AbstractRefreshableApplicationContext類中的refreshBeanFactory()方法,在此方法中,進行CreateFactory()會得到DefaultListableBeanFactory類的一個例項beanFactory,之後會作為方法引數傳入到loadBeanDefinitions(beanFactory)中,這裡其實就是能明顯看到有load字眼啦,繼續一步步往下跟進,進入到真正做事情的方法就是doLoadBeanDefinitions中,這裡會生成一個BeanDefinitionDocumentReader類的例項,之後通過該例項呼叫方法registerBeanDefinitions,依然是要進入到真正做事的doRegisterBeanDefinitions方法中,至此就馬上到了process的部分了,在這個部分會針對傳入的元素進行解析前、中、後的處理,我們進入到解析中的方法:parseBeanDefinitions(root, this.delegate),在解析的方法中,會判斷如果是bean相關namespace的,則會parseDefaultElement,因為這裡是註解的形式,因此其nameSpace不為預設的bean相關的,而是Context的,因此進入到:delegate.parseCustomElement(ele)中,接下來就是具體基於註解進行解析的部分了,即process過程
具體可見下方程式碼:
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 { //註解定義的Context的nameSpace進入到這個分支中 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
process過程:
具體process過程做了哪些事情呢?可分為兩個步驟來說明,首先根據ele的定義得到key,通過key返回對應的namespaceUri,之後根據namespaceUri的解析得到一個NameSpaceHandler的例項handler,之後由具體實現了NameSpaceHandler介面的類NameSpaceHandlerSupport類進行的方法呼叫,即parse方法的呼叫,即離具體的解析更進一步啦~
可見下方程式碼註釋部分:
//常規解析方法 @Nullable public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) { //獲取namespaceUri,例如xml中配置的如果是<context:componet-scan base-packages:xxx> //這裡的ele會得到component-scan,並且值為null,namespaceUri為 http://www.springframework.org/schema/context String namespaceUri = getNamespaceURI(ele); if (namespaceUri == null) { return null; } //基於namespaceUri得到handler,得到handler之後,不同的handler實現了parse方法,到具體的parse去進行呼叫和處理 //因為Handler為:org.springframework.context.config.ContextNamespaceHandler //在resolve的init操作中直接將component-scan的key和對應的ComponentScanBeanDefinitionParser的例項放入到了parser的map中 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } //NameSpaceHandler是介面,具體這裡呼叫的就是得到的handler的實際型別,通過它進行parse呼叫 //實現該Handler的類是:NamespaceHandlerSupport return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
下面繼續說明一下通過namespaceUri的resolve具體是如何實現的的?接下來是對這裡的詳細說明:
首先this.readerContext.getNamespaceHandlerResolver()返回內容為:
public final NamespaceHandlerResolver getNamespaceHandlerResolver() { return this.namespaceHandlerResolver; }
注意這裡定義的是final,final的方法不能被重寫,因為返回的是NamespaceHandlerResolver,發現其是一個介面,因此直接找實現類,點選resolve找到對應的實現類DefaultNamespaceHandler(注意這裡又體現了,最終真的去做事情的,很多都會被命名為Defaultxxx類,或者Simplexxx類)對該方法的實現,resolve中所做事項就是判定是否已有可用解析類,若無則進行初始化init操作,並且返回handler例項
public NamespaceHandler resolve(String namespaceUri) { //先通過handlerMappings的配置進行get獲取,針對傳入的namespaceUri是否存在handler可供使用 Map<String, Object> handlerMappings = getHandlerMappings(); //注意:這裡的從mapping中得到的key為Object的,因為這裡可能為各種不同的具體Handeler,namespaceUri不同,則其value不同 //這裡根據namespaceUri為context的值:org.springframework.context.config.ContextNamespaceHandler,發現不為null Object handlerOrClassName = handlerMappings.get(namespaceUri); //if判斷不為空,跳過此邏輯 if (handlerOrClassName == null) { return null; } //判斷不為NamespaceHandler的例項 else if (handlerOrClassName instanceof NamespaceHandler) { return (NamespaceHandler) handlerOrClassName; } else { //直接將前面的“org.springframework.context.config.ContextNamespaceHandler”轉成String型別的 String className = (String) handlerOrClassName; try { //生成類 Class<?> handlerClass = ClassUtils.forName(className, this.classLoader); if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) { throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface"); } //類的例項化,如果是前一個函式中的<context:Component-scan>這裡得到的Handler為: //org.springframework.context.config.ContextNamespaceHandler NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass); //因為上面是ContextNamespaceHandler,將N種不同的key對應的具體的parser進行new之後作為key放到parsers的map中 //這裡針對key為"component-scan",直接new的例項就是:ComponentScanBeanDefinitionParser namespaceHandler.init(); handlerMappings.put(namespaceUri, namespaceHandler); return namespaceHandler; } catch (ClassNotFoundException ex) { throw new FatalBeanException("Could not find NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", ex); } catch (LinkageError err) { throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", err); } } }
之後通過handler例項進行parse方法呼叫,實現類為:NamespaceHandlerSupport,其中parse方法如下,就是找到真正的parser,從之前的handler的init操作所put的map中將需要解析的key對應的value即解析類例項取出,然後進行真正的解析操作,在parse中會將bean定義註冊代理給scanner類例項,之後通過scanner.doScan()方法呼叫真正完成bean的解析和註冊到容器中
public BeanDefinition parse(Element element, ParserContext parserContext) { //確定是什麼parser,之前已經儲存在Handler的parsers的map中 //find就是找到對應element對應的具體key的具體解析類 BeanDefinitionParser parser = findParserForElement(element, parserContext); //根據具體解析類,直接進行對應的解析呼叫 return (parser != null ? parser.parse(element, parserContext) : null); }
就進入到parser.parse()部分,因為對應key為Component-scan對應的解析類為ComponentScanBeanDefinitionParser類,進入到此類的parse方法中:
public BeanDefinition parse(Element element, ParserContext parserContext) { //根據element為“base=package”得到基礎包路徑 String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE); //解析basePacakge的String值,將佔位符前後綴都去掉 basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage); //這裡其實就是對String的basePackage進行解析,最終得到: //按照分隔符將多個路徑轉換成陣列,並去掉空格以及換行符等 String[] basePackages = StringUtils.tokenizeToStringArray(basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); // Actually scan for bean definitions and register them. //進行beandefinition的掃描並註冊 //將bean定義註冊代理給scanner類處理 ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element); //doScan的呼叫 Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages); registerComponents(parserContext.getReaderContext(), beanDefinitions, element); return null; }
進入到比較重要的部分:ClasspathBeanDefinitionScanner類的doScan()方法,該方法主要做的事情是:針對xml中所做的配置<context:component-scan base-package="cn.lx.controller" />,根據base-package的package路徑下的class檔案,進行遍歷操作,根據其是否具備註解定義,得到beanDefinition的候選集合,針對候選集中的每一個beanDefinition,進行beanName的生成,並且針對是否屬於AbstractBeanDefinition和AnnotatedBeanDefinition,進行相應的屬性設定,之後會通過beanName獲取是否已經容器中是否已經存在此beanName,若無則直接返回true,表示需要進行後續註冊操作,即進入到了register的過程
其中doScan的主要方法及註釋如下:
//針對xml中所做的配置<context:component-scan base-package="cn.lx.controller" /> //根據base-package的package路徑下的class檔案,進行遍歷操作 //根據其是否具備註解定義,得到beanDefinition的候選集合 //針對候選集中的每一個beanDefinition,進行beanName的生成 //並生成BeanDefinitionHolder,進行scopeProxyMode的設定,之後進行register操作 //和非註解形式的合併到一路上了,後續的註冊操作 protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); //針對basePackages中的每一項basePackage做迴圈 for (String basePackage : basePackages) { //得到候選BeanDefinition集合,注意這裡如果沒有@Controller等註解樣式的是不會被加入到候選集中 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); //針對集合中的每一個候選項BeanDefinition進行詳細的解析和處理 for (BeanDefinition candidate : candidates) { //這裡的生成內容會基於scope的字元進行解析,會得到: //scopedName和scopedProxyMode,其中scopeName不單獨說明的話,則預設為singleton,ProxyMode為No ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); //基於解析獲得scope的性質給candidate設值 candidate.setScope(scopeMetadata.getScopeName()); //獲取beanName,會走兩步判斷,有指定beanName的會直接賦值返回,否則會build預設的name,即shortName,例如: //cn.lx.controller.LoginController,則會取LoginController,並且會將首字母小寫,最終形式是:loginController String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); //判斷beanDefinition的型別 if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } //基於註解型別的BeanDefinition //其實沒有看AbstractBeanDefinition和AnnotatedBeanDefinition有何不同,寫具體文章的時候要看 //AnnotatedBeanDefinition是介面 //其中在candidates的生成方法findCandidateComponents()進行candidates的候選集的生成中 //ScannedGenericBeanDefinition是實現了AnnotatedBeanDefinition介面的 //返回的beanDefinition本身就是就是實現了這個介面的,因此必然滿足instanceof if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } //對beanName和候選項的bean進行校驗,以確定是否要進行註冊,還是可能與已有bean存在衝突 if (checkCandidate(beanName, candidate)) { //BeanDefinition的持有者 BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); //設定ScopeProxyMode,若為設定,則預設為No,直接返回beanDefinition definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); //真正標識著下一步就是register的操作了,只是要一步步走到DefaultListableBeanFactory類例項中的註冊方法的呼叫 registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }
register過程:
register過程無所謂是針對有無註解的情況,都是相同的邏輯,通過ClassPathBeanDefinitionScanner類的registerBeanDefition方法,其實呼叫的是BeanDefinitionReaderUtils類的靜態方法registerBeanDefinition,之後再跟進到此靜態方法中,就進入到了DefaultListableBeanFactory類的register操作,就會進行真正的map操作了
protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) { BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry); }