1. 程式人生 > >spring源碼解析2--容器的基本實現

spring源碼解析2--容器的基本實現

ins import ets -s efault def ron for ref

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 class
XmlBeanFactory 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) throws
BeansException { 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--容器的基本實現