程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 【Spring】BeanFactory解析bean詳解

【Spring】BeanFactory解析bean詳解

編輯:關於JAVA

【Spring】BeanFactory解析bean詳解。本站提示廣大學習愛好者:(【Spring】BeanFactory解析bean詳解)文章只能為提供參考,不一定能成為您想要的結果。以下是【Spring】BeanFactory解析bean詳解正文


在該文中來講講Spring框架中BeanFactory解析bean的過程,該文之前在小編原文中有發表過,要看原文的可以直接點擊原文查看,先來看一個在Spring中一個基本的bean定義與使用。
package bean;
public class TestBean {
    private String beanName = "beanName";
    public String getBeanName() {
        return beanName;
    }
    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }
}
Spring配置文件root.xml定義如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-4.1.xsd">

    <bean id="testBean" class="bean.TestBean">
</beans>
下面使用XmlBeanFactory來獲取該bean:
public class BeanTest {

    private static final java.util.logging.Logger logger = LoggerFactory.getLogger(BeanTest.class);

    @Test
    public void getBeanTest() {
        BeanFactory factory = new XmlBeanFactory(new ClassPathResource("root.xml"));
        TestBean bean = factory.getBean("testBean");
        logger.info(bean.getBeanName);
    }
}
這個單元測試運行結果就是輸出beanName,上面就是Spring最基本的bean的獲取操作,這裡我用BeanFactory作為容器來獲取bean的操作並不多見,在企業開發中一般是使用功能更完善的ApplicationContext,這裡先不討論這個,下面重點講解使用BeanFactory獲取bean的過程。   現在就來分析下上面的測試代碼,看看Spring到底為我們做了什麼工作,上面代碼完成功能的流程不外乎如此: 1. 讀取Spring配置文件root.xml; 2. 根據root.xml中的bean配置找到對應的類的配置,並實例化; 3. 調用實例化後的對象輸出結果。   先來看看XmlBeanFactory源碼:
public class XmlBeanFactory extends DefaultListableBeanFactory {

   private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

   public XmlBeanFactory(Resource resource) throws BeansException {
       this(resource, null);
   }

   public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
       super(parentBeanFactory);
       this.reader.loadBeanDefinitions(resource);
   }
}
從上面可以看出XmlBeanFactory繼承了DefaultListableBeanFactory,DefaultListableBeanFactory是Spring注冊加載bean的默認實現,它是整個bean加載的核心部分,XmlBeanFactory與它的不同點就是XmlBeanFactory使用了自定義的XML讀取器XmlBeanDefinitionReader,實現了自己的BeanDefinitionReader讀取。 XmlBeanFactory加載bean的關鍵就在於XmlBeanDefinitionReader,下面看看XmlBeanDefinitionReader的源碼(只列出部分):
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;

    private ProblemReporter problemReporter = new FailFastProblemReporter();

    private ReaderEventListener eventListener = new EmptyReaderEventListener();

    private SourceExtractor sourceExtractor = new NullSourceExtractor();

    private NamespaceHandlerResolver namespaceHandlerResolver;

    private DocumentLoader documentLoader = new DefaultDocumentLoader();

    private EntityResolver entityResolver;

    private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);
}
XmlBeanDefinitionReader繼承自AbstractBeanDefinitionReader,下面是AbstractBeanDefinitionReader的源碼(只列出部分):
public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable, BeanDefinitionReader {

    protected final Log logger = LogFactory.getLog(getClass());

    private final BeanDefinitionRegistry registry;

    private ResourceLoader resourceLoader;

    private ClassLoader beanClassLoader;

    private Environment environment;

    private BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator();
}
XmlBeanDefinitionReader主要通過以下三步來加載Spring配置文件中的bean: 1. 通過繼承自AbstractBeanDefinitionReader中的方法,使用ResourLoader將資源文件(root.xml)路徑轉換為對應的Resource文件; 2. 通過DocumentLoader對Resource文件進行轉換,將Resource文件轉換為Ducument文件; 3. 通過DefaultBeanDefinitionDocumentReader類對Document進行解析,最後再對解析後的Element進行解析。   了解以上基礎後,接下來詳細分析下一開始例子中的代碼:
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("root.xml"));
先看看下面XmlBeanFactory初始化的時序圖來進一步了解這段代碼的執行, 在這裡可以看出BeanTest測試類通過向ClassPathResource的構造方法傳入spring的配置文件構造一個Resource資源文件的實例對象,再通過這個Resource資源文件來構造我們想要的XmlBeanFactory實例。在前面XmlBeanFactory源碼中的構造方法可以看出,
public XmlBeanFactory(Resource resource) throws BeansException {
     this(resource, null);
}

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
     super(parentBeanFactory);
     this.reader.loadBeanDefinitions(resource);
}
this.reader.loadBeanDefinition(resource)就是資源加載真正的實現,時序圖中XmlBeanDefinitionReader加載數據就是在這裡完成的。   接下來跟進this.reader.loadBeanDefinition(resource)方法裡面,
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    @Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
    }
}
在loadBeanDefinition(resource)方法裡對資源文件resource使用EncodedResource進行編碼處理後繼續傳入loadBeanDefinitions方法,繼續跟進loadBeanDefinitions(new EncodedResource(resource))方法源碼:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    Assert.notNull(encodedResource, "EncodedResource must not be null");
    if (logger.isInfoEnabled()) {
        logger.info("Loading XML bean definitions from " + encodedResource.getResource());
    }

    // 通過屬性記錄已加載的資源
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
        currentResources = new HashSet<EncodedResource>(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }
    if (!currentResources.add(encodedResource)) {
        throw new BeanDefinitionStoreException(
                "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }
    try {
        // 從resource中獲取對應的InputStream,用於下面構造InputSource
        InputStream inputStream = encodedResource.getResource().getInputStream();
        try {
            InputSource inputSource = new InputSource(inputStream);
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            // 調用doLoadBeanDefinitions方法
            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
        }
        finally {
            inputStream.close();
        }
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException(
                "IOException parsing XML document from " + encodedResource.getResource(), ex);
    }
    finally {
        currentResources.remove(encodedResource);
        if (currentResources.isEmpty()) {
            this.resourcesCurrentlyBeingLoaded.remove();
        }
    }
}
繼續跟進doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法,這是整個bean加載過程的核心方法,在這個方法執行bean的加載。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
        throws BeanDefinitionStoreException {
    try {
        Document doc = doLoadDocument(inputSource, resource);
        return registerBeanDefinitions(doc, resource);
    }
    /* 省略一堆catch */
}
跟進doLoadDocument(inputSource, resource)源碼:
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
    return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
            getValidationModeForResource(resource), isNamespaceAware());
}
在doLoadDocument(inputSource, resource)方法裡就使用到了前面講的documentLoader加載Document,這裡DocumentLoader是個接口,真正調用的是其實現類DefaultDocumentLoader的loadDocument方法,跟進源碼:
public class DefaultDocumentLoader implements DocumentLoader {

    @Override
    public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
            ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

        DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
        if (logger.isDebugEnabled()) {
            logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
        }
        DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
        return builder.parse(inputSource);
    }
}
從源碼可以看出這裡先創建DocumentBuilderFactory,再用它創建DocumentBuilder,進而解析inputSource來返回Document對象。得到Document對象後就可以准備注冊我們的Bean信息了。   在上面的doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法中拿到Document對象後下面就是執行registerBeanDefinitions(doc, resource)方法了,看源碼:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    documentReader.setEnvironment(getEnvironment());
    // 還沒注冊bean前的BeanDefinition加載個數
    int countBefore = getRegistry().getBeanDefinitionCount();
    // 加載注冊bean
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    // 本次加載注冊的BeanDefinition個數
    return getRegistry().getBeanDefinitionCount() - countBefore;
}
這裡的doc就是上面的loadDocument方法加載轉換來的,從上面可以看出主要工作是交給BeanDefinitionDocumentReader的registerBeanDefinitions()方法實現的,這裡BeanDefinitionDocumentReader是個接口,注冊bean功能在默認實現類DefaultBeanDefinitionDocumentReader的該方法實現,跟進它的源碼:
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    logger.debug("Loading bean definitions");
    Element root = doc.getDocumentElement();
    doRegisterBeanDefinitions(root);
}
到這裡通過doc.getDocumentElement()獲得Element對象後,交給doRegisterBeanDefinitions()方法後就是真正執行XML文檔的解析了,跟進doRegisterBeanDefinitions()方法源碼:
protected void doRegisterBeanDefinitions(Element root) {
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);

    if (this.delegate.isDefaultNamespace(root)) {
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                    profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                return;
            }
        }
    }

    preProcessXml(root);
    parseBeanDefinitions(root, this.delegate);
    postProcessXml(root);

    this.delegate = parent;
}
到這裡處理流程就很清晰了,先是對profile進行處理,之後就通過parseBeanDefinitions()方法進行文檔的解析操作,跟進parseBeanDefinitions()方法源碼:
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;
                // 下面對bean進行處理
                if (delegate.isDefaultNamespace(ele)) {
                    parseDefaultElement(ele, delegate);
                }
                else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        delegate.parseCustomElement(root);
    }
}
上面if-else語句塊中的parseDefaultElement(ele, delegate)和delegate.parseCustomElement(ele)就是對Spring配置文件中的默認命名空間和自定義命名空間進行解析用的。在Spring的XML配置中,默認Bean聲明就如前面定義的:
<bean id="testBean" class="bean.TestBean">
自定義的Bean聲明如:
<tx:annotation-driven />

XmlBeanFactory加載bean的整個過程基本就講解到這裡了。

   
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved