1. 程式人生 > >spring原始碼深度解析---預設標籤解析(下)

spring原始碼深度解析---預設標籤解析(下)

spring原始碼深度解析—預設標籤解析(下)

spring原始碼深度解析—預設標籤解析(上)中我們已經完成了從xml配置檔案到BeanDefinition的轉換,轉換後的例項是GenericBeanDefinition的例項。而GenericBeanDefinition是AbstractBeanDefinition的子類,那我們優先看下AbstractBeanDefinition中的屬性,程式碼如下:

public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor implements
BeanDefinition, Cloneable {
public static final String SCOPE_DEFAULT = ""; public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO; public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME; public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE; public
static final int AUTOWIRE_CONSTRUCTOR = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR; @Deprecated public static final int AUTOWIRE_AUTODETECT = AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT; public static final int DEPENDENCY_CHECK_NONE = 0; public static final int DEPENDENCY_CHECK_OBJECTS = 1
; public static final int DEPENDENCY_CHECK_SIMPLE = 2; public static final int DEPENDENCY_CHECK_ALL = 3; public static final String INFER_METHOD = "(inferred)"; @Nullable private volatile Object beanClass; //bean的作用範圍,對應了bean的scope屬性 @Nullable private String scope = SCOPE_DEFAULT; //是否抽象,對應bean的abstract屬性 private boolean abstractFlag = false; //是否延遲載入,對應了bean的lazy-inti屬性 private boolean lazyInit = false; //對應bean的autowire模式 private int autowireMode = AUTOWIRE_NO; //依賴檢查 private int dependencyCheck = DEPENDENCY_CHECK_NONE; //用來表示一個bean的例項化依靠另一個bean先例項化,對應bean屬性的depend-on @Nullable private String[] dependsOn; //autowire-candicate屬性設定為false,這樣容器在查詢自動裝配物件時將不考慮bean,則它不會被考慮作為其他bean自動裝配的候選者,但是該bean本身還是可以使用自動裝配來注入其他bean的 //對應bean屬性的autowire-candidate private boolean autowireCandidate = true; //自動裝配時當出現多個bean候選者時,將作為首選者,對應bean屬性primary private boolean primary = false; //用於記錄Qualifier,對應子元素qualifier private final Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<>(0); @Nullable private Supplier<?> instanceSupplier; //允許訪問非公開的構造器和方法,程式設定 private boolean nonPublicAccessAllowed = true; private boolean lenientConstructorResolution = true; //對應bean屬性的factory-bean @Nullable private String factoryBeanName; //對應bean屬性的factory-method @Nullable private String factoryMethodName; //記錄建構函式注入屬性,對應bean屬性constructor-arg @Nullable private ConstructorArgumentValues constructorArgumentValues; //普通屬性集合 @Nullable private MutablePropertyValues propertyValues; //方法重寫的持有者,記錄lookup-method replaced-method元素 @Nullable private MethodOverrides methodOverrides; //初始化方法,對應bean屬性的init-method @Nullable private String initMethodName; //銷燬方法,對應bean屬性的deatory-method @Nullable private String destroyMethodName; //是否執行init-method程式設定 private boolean enforceInitMethod = true; //是否執行deatory-method程式設定 private boolean enforceDestroyMethod = true; //是否是使用者定義的而不是應用程式本身定義的,建立AOP時候為true,程式設定 private boolean synthetic = false; //定義這個bean的應用,APPLICATION:使用者;INFRASTRUCTURE:完全內部使用,與使用者無關,SUPPORT:某些複雜配置的一部分 private int role = BeanDefinition.ROLE_APPLICATION; //bean的描述資訊 @Nullable private String description; //bean定義的資源 @Nullable private Resource resource;

1. 預設標籤中的自定義標籤解析

在上篇博文中我們已經分析了對於預設標籤的解析,我們繼續看戲之前的程式碼,如下圖片中有一個方法:delegate.decorateBeanDefinitionIfRequired(ele, bdHolder)
這裡寫圖片描述
這個方法的作用是什麼呢?首先我們看下這種場景,如下配置檔案:

 <bean id="demo" class="com.yhl.myspring.demo.bean.MyBeanDemo">
     <property name="beanName" value="bean demo1"/>
     <meta key="demo" value="demo"/>
     <mybean:user name="mybean"/>
 </bean>

這個配置檔案中有個自定義的標籤,decorateBeanDefinitionIfRequired方法就是用來處理這種情況的,其中的null是用來傳遞父級BeanDefinition的,我們進入到其方法體:

public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {
    return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
}
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
        Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {

    BeanDefinitionHolder finalDefinition = definitionHolder;

    // Decorate based on custom attributes first.
    NamedNodeMap attributes = ele.getAttributes();
    for (int i = 0; i < attributes.getLength(); i++) {
        Node node = attributes.item(i);
        finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
    }

    // Decorate based on custom nested elements.
    NodeList children = ele.getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
        Node node = children.item(i);
        if (node.getNodeType() == Node.ELEMENT_NODE) {
            finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
        }
    }
    return finalDefinition;
}

我們看到上面的程式碼有兩個遍歷操作,一個是用於對所有的屬性進行遍歷處理,另一個是對所有的子節點進行處理,兩個遍歷操作都用到了decorateIfRequired(node, finalDefinition, containingBd);方法,我們繼續跟蹤程式碼,進入方法體:

public BeanDefinitionHolder decorateIfRequired(
        Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {

    String namespaceUri = getNamespaceURI(node);
    if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler != null) {
            BeanDefinitionHolder decorated =
                    handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
            if (decorated != null) {
                return decorated;
            }
        }
        else if (namespaceUri.startsWith("http://www.springframework.org/")) {
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
        }
        else {
            // A custom namespace, not to be handled by Spring - maybe "xml:...".
            if (logger.isDebugEnabled()) {
                logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
            }
        }
    }
    return originalDef;
}

2. 註冊解析的BeanDefinition

對於配置檔案,解析和裝飾完成之後,對於得到的beanDefinition已經可以滿足後續的使用要求了,還剩下注冊,也就是processBeanDefinition函式中的BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,getReaderContext().getRegistry())程式碼的解析了。進入方法體:

public static void registerBeanDefinition(
        BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
        throws BeanDefinitionStoreException {
    // Register bean definition under primary name.
    String beanName = definitionHolder.getBeanName();
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

    // Register aliases for bean name, if any.
    String[] aliases = definitionHolder.getAliases();
    if (aliases != null) {
        for (String alias : aliases) {
            registry.registerAlias(beanName, alias);
        }
    }
}

從上面的程式碼我們看到是用了beanName作為唯一標示進行註冊的,然後註冊了所有的別名aliase。而beanDefinition最終都是註冊到BeanDefinitionRegistry中,接下來我們具體看下注冊流程。

3. 通過beanName註冊BeanDefinition

在spring中除了使用beanName作為key將BeanDefinition放入Map中還做了其他一些事情,我們看下方法registerBeanDefinition程式碼,BeanDefinitionRegistry是一個介面,他有三個實現類,DefaultListableBeanFactory、SimpleBeanDefinitionRegistry、GenericApplicationContext,其中SimpleBeanDefinitionRegistry非常簡單,而GenericApplicationContext最終也是使用的DefaultListableBeanFactory中的實現方法,我們看下程式碼:

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
        throws BeanDefinitionStoreException {

    Assert.hasText(beanName, "Bean name must not be empty");
    Assert.notNull(beanDefinition, "BeanDefinition must not be null");

    if (beanDefinition instanceof AbstractBeanDefinition) {
        try {
            ((AbstractBeanDefinition) beanDefinition).validate();
        }
        catch (BeanDefinitionValidationException ex) {
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                    "Validation of bean definition failed", ex);
        }
    }

    BeanDefinition oldBeanDefinition;

    oldBeanDefinition = this.beanDefinitionMap.get(beanName);
    if (oldBeanDefinition != null) {
        if (!isAllowBeanDefinitionOverriding()) {
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                    "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
                    "': There is already [" + oldBeanDefinition + "] bound.");
        }
        else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
            // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
            if (this.logger.isWarnEnabled()) {
                this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
                        "' with a framework-generated bean definition: replacing [" +
                        oldBeanDefinition + "] with [" + beanDefinition + "]");
            }
        }
        else if (!beanDefinition.equals(oldBeanDefinition)) {
            if (this.logger.isInfoEnabled()) {
                this.logger.info("Overriding bean definition for bean '" + beanName +
                        "' with a different definition: replacing [" + oldBeanDefinition +
                        "] with [" + beanDefinition + "]");
            }
        }
        else {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Overriding bean definition for bean '" + beanName +
                        "' with an equivalent definition: replacing [" + oldBeanDefinition +
                        "] with [" + beanDefinition + "]");
            }
        }
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }
    else {
        if (hasBeanCreationStarted()) {
            // Cannot modify startup-time collection elements anymore (for stable iteration)
            synchronized (this.beanDefinitionMap) {
                this.beanDefinitionMap.put(beanName, beanDefinition);
                List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                updatedDefinitions.addAll(this.beanDefinitionNames);
                updatedDefinitions.add(beanName);
                this.beanDefinitionNames = updatedDefinitions;
                if (this.manualSingletonNames.contains(beanName)) {
                    Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
                    updatedSingletons.remove(beanName);
                    this.manualSingletonNames = updatedSingletons;
                }
            }
        }
        else {
            // Still in startup registration phase
            this.beanDefinitionMap.put(beanName, beanDefinition);
            this.beanDefinitionNames.add(beanName);
            this.manualSingletonNames.remove(beanName);
        }
        this.frozenBeanDefinitionNames = null;
    }

    if (oldBeanDefinition != null || containsSingleton(beanName)) {
        resetBeanDefinition(beanName);
    }
}

分析上面程式碼我們簡單總結下對於beanDefinition的註冊的處理步驟:
(1)對AbstractBeanDefinition的校驗,在解析XML檔案的時候我們提過校驗,但是此校驗非彼校驗,之前的校驗是針對於XML格式的校驗,而此時的校驗是針對AbstractBeanDefinition的methodOverrides屬性的。
(2)對beanName已經註冊的情況的處理,如果設定了不允許bean的覆蓋,則需要丟擲異常,否陳直接覆蓋
(3)加入map快取
(4)清除解析之前留下的對應beanName的快取

4. 通過別名註冊BeanDefinition

通過別名註冊BeanDefinition最終是在SimpleBeanDefinitionRegistry中實現的,我們看下程式碼:

public void registerAlias(String name, String alias) {
    Assert.hasText(name, "'name' must not be empty");
    Assert.hasText(alias, "'alias' must not be empty");
    synchronized (this.aliasMap) {
        if (alias.equals(name)) {
            this.aliasMap.remove(alias);
        }
        else {
            String registeredName = this.aliasMap.get(alias);
            if (registeredName != null) {
                if (registeredName.equals(name)) {
                    // An existing alias - no need to re-register
                    return;
                }
                if (!allowAliasOverriding()) {
                    throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" +
                            name + "': It is already registered for name '" + registeredName + "'.");
                }
            }
            checkForAliasCircle(name, alias);
            this.aliasMap.put(alias, name);
        }
    }
}

上述程式碼的流程總結如下:
(1)alias與beanName相同情況處理,若alias與beanName併名稱相同則不需要處理並刪除原有的alias
(2)alias覆蓋處理。若aliasName已經使用並已經指向了另一beanName則需要使用者的設定進行處理
(3)alias迴圈檢查,當A->B存在時,若再次出現A->C->B時候則會丟擲異常。

5. 註冊完成後通知監聽器Bean註冊完成

這裡寫圖片描述
從上圖中我們看到在註冊完bean後使用getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder))方法通知監聽器bean註冊完成。

6. alias標籤的解析

對應bean標籤的解析是最核心的功能,對於alias、import、beans標籤的解析都是基於bean標籤解析的,接下來我就分析下alias標籤的解析。我們回到 parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)方法,繼續看下方法體,如下圖所示:
這裡寫圖片描述
對bean進行定義時,除了用id來 指定名稱外,為了提供多個名稱,可以使用alias標籤來指定。而所有這些名稱都指向同一個bean。在XML配置檔案中,可用單獨的元素來完成bean別名的定義。如配置檔案中定義了一個javaBean:

<bean id="demo" class="com.yhl.myspring.demo.bean.MyBeanDemo">
        <property name="beanName" value="bean demo1"/>
    </bean>

要給這個javaBean增加別名,以方便不同的物件來呼叫。我們可以直接使用bean標籤中的name屬性,如下:

    <bean id="demo" class="com.yhl.myspring.demo.bean.MyBeanDemo" name="demo1,demo2">
        <property name="beanName" value="bean demo1"/>
    </bean>

在Spring還有另外一種宣告別名的方式:
我們具體看下alias標籤的解析過程,解析使用的方法processAliasRegistration(ele),方法體如下:

protected void processAliasRegistration(Element ele) {
    String name = ele.getAttribute(NAME_ATTRIBUTE);
    String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
    boolean valid = true;
    if (!StringUtils.hasText(name)) {
        getReaderContext().error("Name must not be empty", ele);
        valid = false;
    }
    if (!StringUtils.hasText(alias)) {
        getReaderContext().error("Alias must not be empty", ele);
        valid = false;
    }
    if (valid) {
        try {
            getReaderContext().getRegistry().registerAlias(name, alias);
        }
        catch (Exception ex) {
            getReaderContext().error("Failed to register alias '" + alias +
                    "' for bean with name '" + name + "'", ele, ex);
        }
        getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
    }
}

通過程式碼可以發現解析流程與bean中的alias解析大同小異,都是講beanName與別名alias組成一對註冊到registry中。跟蹤程式碼最終使用了SimpleAliasRegistry中的registerAlias(String name, String alias)方法,方法體如下:

public void registerAlias(String name, String alias) {
    Assert.hasText(name, "'name' must not be empty");
    Assert.hasText(alias, "'alias' must not be empty");
    synchronized (this.aliasMap) {
        if (alias.equals(name)) {
            this.aliasMap.remove(alias);
        }
        else {
            String registeredName = this.aliasMap.get(alias);
            if (registeredName != null) {
                if (registeredName.equals(name)) {
                    // An existing alias - no need to re-register
                    return;
                }
                if (!allowAliasOverriding()) {
                    throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" +
                            name + "': It is already registered for name '" + registeredName + "'.");
                }
            }
            checkForAliasCircle(name, alias);
            this.aliasMap.put(alias, name);
        }
    }
}

7. import標籤的解析

對於Spring配置檔案的編寫,經歷過大型專案的人都知道,裡面有太多的配置檔案了。基本採用的方式都是分模組,分模組的方式很多,使用import就是其中一種,例如我們可以構造這樣的Spring配置檔案:

<?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.xsd">

    <bean id="demo" class="com.yhl.myspring.demo.bean.MyBeanDemo" name="demo1,demo2">
        <property name="beanName" value="bean demo1"/>
    </bean>
    <import resource="lookup-method.xml"/>
    <import resource="replaced-method.xml"/>
</beans>

applicationContext.xml檔案中使用import方式匯入有模組配置檔案,以後若有新模組的加入,那就可以簡單修改這個檔案了。這樣大大簡化了配置後期維護的複雜度,並使配置模組化,易於管理。我們來看看Spring是如何解析import配置檔案的呢。解析import標籤使用的是importBeanDefinitionResource(ele),進入方法體:

protected void importBeanDefinitionResource(Element ele) {
    //獲取resource屬性,如果不存在則不處理
    String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
    if (!StringUtils.hasText(location)) {
        getReaderContext().error("Resource location must not be empty", ele);
        return;
    }
    //解析系統屬性,格式如:${user.dir}
    // Resolve system properties: e.g. "${user.dir}"
    location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);

    Set<Resource> actualResources = new LinkedHashSet<>(4);
    //判斷location是絕對URL還是相對URI
    // Discover whether the location is an absolute or relative URI
    boolean absoluteLocation = false;
    try {
        absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
    }
    catch (URISyntaxException ex) {
        // cannot convert to an URI, considering the location relative
        // unless it is the well-known Spring prefix "classpath*:"
    }
    //如果是絕對URL則根據地址直接載入檔案
    // Absolute or relative?
    if (absoluteLocation) {
        try {
            int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
            if (logger.isDebugEnabled()) {
                logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]");
            }
        }
        catch (BeanDefinitionStoreException ex) {
            getReaderContext().error(
                    "Failed to import bean definitions from URL location [" + location + "]", ele, ex);
        }
    }
    else {
        // No URL -> considering resource location as relative to the current file.
        try {
            int importCount;
            Resource relativeResource = getReaderContext().getResource().createRelative(location);
            if (relativeResource.exists()) {
                importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
                actualResources.add(relativeResource);
            }
            else {
                String baseLocation = getReaderContext().getResource().getURL().toString();
                importCount = getReaderContext().getReader().loadBeanDefinitions(
                        StringUtils.applyRelativePath(baseLocation, location), actualResources);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]");
            }
        }
        catch (IOException ex) {
            getReaderContext().error("Failed to resolve current resource location", ele, ex);
        }
        catch (BeanDefinitionStoreException ex) {
            getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]",
                    ele, ex);
        }
    }
    Resource[] actResArray = actualResources.toArray(new Resource[0]);
    getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}

方法的流程總結如下:
(1)獲取resource屬性所表示的路徑
(2)解析路徑中的系統屬性
(3)判斷location是絕對路徑還是相對路徑
(4)如果是絕對路徑則遞迴呼叫bean的解析過程,進行另一次解析
(5)如果是相對路徑則計算出絕對路徑進行解析
(6)通知監聽器,解析完成

8. 嵌入式beans標籤解析

對於嵌入式的beans,解析與單獨配置檔案的解析沒有太大卻別,無非是呼叫beans的解析過程