前面的文章寫了xml中直接配置bean進行IOC的過程解析,接下來會針對註解進行IOC容器初始化的過程解析node
由於會與以前的內容存在部分重疊,所以會針對相同的部分簡略帶過,針對不一樣的部分作重點說明:spring
applicationContext.xml配置添加:數組
<context:component-scan base-package="cn.lx.controller" />app
代碼中配置註解修改:函數
@Controller public class TestController { @RequestMapping("/test.form") public void execute(){ return ; } }
入口部分:ContextLoaderListener類中的contextInitialized,進入到ContextLoader類的initWebApplicationContextpost
方法中,該方法中執行的關鍵方法是:configureAndRefreshWebApplicationContext()進入到ConfigurableWebApplicationContextui
類的實例wac.refresh()調用中,至此進入到具體接下來的load階段了this
進入到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過程spa
具體可見下方代碼:代理
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過程作了哪些事情呢?可分爲兩個步驟來講明,首先根據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過程無所謂是針對有無註解的狀況,都是相同的邏輯,經過ClassPathBeanDefinitionScanner類的registerBeanDefition方法,其實調用的是BeanDefinitionReaderUtils類的靜態方法registerBeanDefinition,以後再跟進到此靜態方法中,就進入到了DefaultListableBeanFactory類的register操做,就會進行真正的map操做了
protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) { BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry); }