spring源碼解析2--容器的基本實現
spring的主要特性是IOC,實現IOC的關鍵是bean,而更關鍵的是如何bean的管理容器,也就是BeanFactory,本文的目標是弄清楚BeanFactory具體是怎麽樣的存在。
先看下最簡單的獲取bean的案例,代碼如下:
1 public static void main(String[] args){ 2 BeanFactory factory = new XmlBeanFactory(new ClassPathResource("spring-beans.xml")); 3 User user = (User) factory.getBean("user");4 System.out.println(JSON.toJSON(user).toString()); 5 }
首先是讀取spring的配置文件,創建BeanFactory實例,如何直接從BeanFactory實例中獲取指定名稱的bean。接下來就從這幾行簡單的代碼入手,分析下BeanFactory。
BeanFactory實例是通過XmlBeanFactory來創建的,很明顯XmlBeanFactory是BeanFactory的子類,繼承關系圖如下:
那麽先就從XmlBeanFactory的構造方法開始,源碼如下:
1 public classXmlBeanFactory extends DefaultListableBeanFactory { 2 3 private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); 4 5 public XmlBeanFactory(Resource resource) throws BeansException { 6 this(resource, null); 7 } 8 9 public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throwsBeansException { 10 super(parentBeanFactory); 11 this.reader.loadBeanDefinitions(resource); 12 } 13 14 }
第5行的構造方法調用第9行的構造方法,首先是執行父類的構造方法,然後執行XmlBeanFefinitionReader的loadBeanFefinitions(resource)方法,這個方法顯然就是加載bean配置文件的方法。接下來跟蹤查看,源碼如下:
1 @Override 2 public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { 3 return loadBeanDefinitions(new EncodedResource(resource)); 4 }
通過EncodeResource來封裝Resource,在調用重載的方法
1 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { 2 Assert.notNull(encodedResource, "EncodedResource must not be null"); 3 //校驗Resource參數 4 if (logger.isInfoEnabled()) { 5 logger.info("Loading XML bean definitions from " + encodedResource.getResource()); 6 } 7 8 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); 9 if (currentResources == null) { 10 currentResources = new HashSet<>(4); 11 this.resourcesCurrentlyBeingLoaded.set(currentResources); 12 } 13 if (!currentResources.add(encodedResource)) { 14 throw new BeanDefinitionStoreException( 15 "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); 16 } 17 try { 18 //獲取Resource的InputSteam流 19 InputStream inputStream = encodedResource.getResource().getInputStream(); 20 try { 21 //通過InputSteam構造InputSource 22 InputSource inputSource = new InputSource(inputStream); 23 if (encodedResource.getEncoding() != null) { 24 inputSource.setEncoding(encodedResource.getEncoding()); 25 } 26 //最終執行doLoadBeanDefinitions方法 27 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); 28 } 29 finally { 30 inputStream.close(); 31 } 32 } 33 catch (IOException ex) { 34 throw new BeanDefinitionStoreException( 35 "IOException parsing XML document from " + encodedResource.getResource(), ex); 36 } 37 finally { 38 currentResources.remove(encodedResource); 39 if (currentResources.isEmpty()) { 40 this.resourcesCurrentlyBeingLoaded.remove(); 41 } 42 } 43 }
這次封裝的EncodedResource作用主要是對XML配置文件的編碼格式進行處理,然後最終執行了doLoadBeanDefinitions方法
1 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) 2 throws BeanDefinitionStoreException { 3 try { 4 //通過InputSource和Resource得到一個Document對象,然後執行註冊bean的方法 5 Document doc = doLoadDocument(inputSource, resource); 6 return registerBeanDefinitions(doc, resource); 7 } 8 catch (BeanDefinitionStoreException ex) { 9 throw ex; 10 } 11 catch (SAXParseException ex) { 12 throw new XmlBeanDefinitionStoreException(resource.getDescription(), 13 "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); 14 } 15 catch (SAXException ex) { 16 throw new XmlBeanDefinitionStoreException(resource.getDescription(), 17 "XML document from " + resource + " is invalid", ex); 18 } 19 catch (ParserConfigurationException ex) { 20 throw new BeanDefinitionStoreException(resource.getDescription(), 21 "Parser configuration exception parsing XML from " + resource, ex); 22 } 23 catch (IOException ex) { 24 throw new BeanDefinitionStoreException(resource.getDescription(), 25 "IOException parsing XML document from " + resource, ex); 26 } 27 catch (Throwable ex) { 28 throw new BeanDefinitionStoreException(resource.getDescription(), 29 "Unexpected exception parsing XML document from " + resource, ex); 30 } 31 }
該方法只有兩行有效邏輯代碼,首先是加載XML文件得到一個Document對象,然後根據Document對象來註冊Bean信息,一步步來看,先看如何生成Document對象。
1 private DocumentLoader documentLoader = new DefaultDocumentLoader(); 2 3 protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { 4 return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, 5 getValidationModeForResource(resource), isNamespaceAware()); 6 }
調用了DocumentLoader的實例來進行加載,DocumentLoader接口的loadDocument方法
1 public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, 2 ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { 3 4 //這裏顯然是工廠模式+建造者模式,先創建DocumentBuilder工廠,如何得到DocumentBuilder,最終執行parse方法解析xml配置文件得到Document對象 5 DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); 6 if (logger.isDebugEnabled()) { 7 logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); 8 } 9 DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); 10 return builder.parse(inputSource); 11 }
1 public Document parse(InputSource is) throws SAXException, IOException { 2 if (is == null) { 3 throw new IllegalArgumentException( 4 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, 5 "jaxp-null-input-source", null)); 6 } 7 if (fSchemaValidator != null) { 8 if (fSchemaValidationManager != null) { 9 fSchemaValidationManager.reset(); 10 fUnparsedEntityHandler.reset(); 11 } 12 resetSchemaValidator(); 13 } 14 //上面的代碼都是校驗,核心是下面的三行,通過DomParser來獲取Document對象 15 domParser.parse(is); 16 Document doc = domParser.getDocument(); 17 domParser.dropDocumentReferences(); 18 return doc; 19 }
解析配置文件得到了Document對象,接下來就是通過Document來註冊beanl了。回到XmlBeanDefinitionReader的RegisterBeanDefinitions方法
1 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { 2 //創建BeanDefinitionDocumentReader實例 3 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); 4 //統計當前已經加載過的BeanDefinition個數 5 int countBefore = getRegistry().getBeanDefinitionCount(); 6 //加載及註冊bean 7 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); 8 //得到本次加載的BeanFinition個數 9 return getRegistry().getBeanDefinitionCount() - countBefore; 10 }
核心是BeanDefinitionDocumentReader接口的registerBeanDefinitions方法,代碼如下:
1 public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { 2 this.readerContext = readerContext; 3 logger.debug("Loading bean definitions"); 4 Element root = doc.getDocumentElement(); 5 doRegisterBeanDefinitions(root); 6 }
首先是獲取Document的root,然後將root元素傳遞給下面的方法繼續執行,這裏的root元素就是配置文件中的<beans>標簽
1 protected void doRegisterBeanDefinitions(Element root) { 2 //專門處理解析 3 BeanDefinitionParserDelegate parent = this.delegate; 4 this.delegate = createDelegate(getReaderContext(), root, parent); 5 6 if (this.delegate.isDefaultNamespace(root)) { 7 //處理<beans>標簽中的profile屬性 8 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); 9 if (StringUtils.hasText(profileSpec)) { 10 String[] specifiedProfiles = StringUtils.tokenizeToStringArray( 11 profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); 12 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { 13 if (logger.isInfoEnabled()) { 14 logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + 15 "] not matching: " + getReaderContext().getResource()); 16 } 17 return; 18 } 19 } 20 } 21 22 preProcessXml(root);//代碼為空(模板方法設計模式:提供給子類實現,如果需要在bean的解析前後做一些處理的話) 23 parseBeanDefinitions(root, this.delegate);//Bean的解析 24 postProcessXml(root);//代碼為空 25 26 this.delegate = parent; 27 }
發現bean的註冊方法應該是parseBeanDefinitions方法,繼續往下
1 protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { 2 if (delegate.isDefaultNamespace(root)) { 3 //獲取<beans>標簽的子節點也就是所有的<bean>標簽 4 NodeList nl = root.getChildNodes(); 5 for (int i = 0; i < nl.getLength(); i++) { 6 Node node = nl.item(i); 7 if (node instanceof Element) { 8 Element ele = (Element) node; 9 if (delegate.isDefaultNamespace(ele)) { 10 //如果是默認命名空間的bean 11 parseDefaultElement(ele, delegate); 12 } 13 else { 14 //如果是自定義命名空間的bean 15 delegate.parseCustomElement(ele); 16 } 17 } 18 } 19 } 20 else { 21 //自定義命名空間的bean 22 delegate.parseCustomElement(root); 23 } 24 }
這裏是邏輯很清楚,獲取root下的子節點遍歷,判斷是否是默認命名空間的元素來分別處理。判斷是否是默認命名空間的方式是取Node的namespanceUrl來和默認命名空間的地址進行比較,默認地址為:
public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";
像<bean id="userService" class="com.test.UserService">這種寫法就是默認標簽,而如<tx:annotation-driven>這種就是自定義的寫法。
總結:本文從BeanFactory的初始化開始,解析XML配置文件,生成Docuemnt對象,解析Document中的標簽,按標簽的類型來進行區分解析,而兩種的解析方式截然不同,接下來就分別解析。
spring源碼解析2--容器的基本實現