1. 程式人生 > >Spring原始碼系列 — BeanDefinition擴充套件點

Spring原始碼系列 — BeanDefinition擴充套件點

前言

前文介紹了Spring Bean的生命週期,也算是XML IOC系列的完結。但是Spring的博大精深,還有很多盲點需要摸索。整合前面的系列文章,從Resource到BeanDefinition,再到容器擴充套件點,最後到Bean創鍵,這個過程中無處不存在Spring預留的擴充套件口。

本篇文章介紹Spring的另一種擴充套件點:BeanDefinition擴充套件點,該擴充套件點是為處理BeanDefinition而設計。本文主要從以下幾點分析:

  • BeanDefinition擴充套件點的幾種方式
  • BeanDefinition擴充套件點實戰
  • BeanDefinition擴充套件點的原理

BeanDefinition擴充套件點的幾種方式

Spring中針對向上下文中新增BeanDefinition、修改上下文中的BeanDefinition可謂是提供了豐富的擴充套件點。既有針對XML配置的,又有針對註解配置的Bean,甚至還有自定義XML標籤的。這裡總結了,共有以下幾種方式:

  1. BeanDefinitionRegistryPostProcessor方式
  2. BeanFactoryPostProcessor方式
  3. ImportBeanDefinitionRegistrar方式
  4. BeanDefinitionParser方式
BeanDefinitionRegistryPostProcessor方式

從命名上也可以看出一些端倪,BeanDefinitionRegistryPostProcessor是BeanDefinition註冊後置處理器,它本身是BeanFactoryPostProcessor的擴充套件,允許在BeanFactoryPostProcessor處理前向上下文中註冊更多的BeanDefinition。

BeanFactoryPostProcessor方式

BeanFactoryPostProcessor是容器的擴充套件點,用於更進一步處理上下文中的BeanDefinition,如果對其還不甚瞭解,請移步至我的另一篇文章Spring原始碼系列 — 容器Extend Point(一)

ImportBeanDefinitionRegistrar方式

ImportBeanDefinitionRegistrar也是BeanDefinition註冊器,用於向上下文註冊更多的BeanDefinition。不過它是被應用在註解處理BeanDefinition的場景中,即自定義註解,然後利用ImportBeanDefinitionRegistrar其實現向上下文中註冊自定義註解標註的Bean定義。

BeanDefinitionParser方式

BeanDefinitionParser是BeanDefinition解析器,它是Spring提供為擴充套件解析XML配置的Bean而設計。它不僅能夠解析XML向上下文中註冊更多BeanDefiniion,同時還支援自定義XML Tag。

BeanDefinition擴充套件點實戰

上節整理了Spring中提供處理BeanDefinition的幾種擴充套件方式,為了更好的理解和應用這些擴充套件點,本節將從實戰的角度再度理解這些擴充套件方式。

Notes:
關於BeanFactoryPostProcessor的擴充套件實戰本節不再做說明,在前文的容器擴張點中已經詳細介紹其原理,並利用PropertySourcesPlaceholderConfigurer案例進行了分析。這裡不再贅述。

基於BeanDefinitionRegistryPostProcessor擴充套件

首先定義BeanDefinitionRegistryPostProcessor實現類MyBdRegistryPostProcessor,實現其postProcessBeanDefinitionRegistry介面:

/**
 * 用於演示BeanDefinitionRegistryPostProcessor擴充套件點
 *
 * @author huaijin
 */
public class MyBdRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        try {
            // 建立自定義的BeanDefinition
            String bdClassName = MyBeanUsedBdRegistryPostProcessor.class.getName();
            AbstractBeanDefinition bd = BeanDefinitionReaderUtils
                    .createBeanDefinition(null, bdClassName, ClassUtils.getDefaultClassLoader());
            // 設定BeanDefinition屬性:單例、非惰性
            bd.setScope(AbstractAutowireCapableBeanFactory.SCOPE_SINGLETON);
            bd.setLazyInit(false);
            // 設定Bean的屬性值
            MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
            PropertyValue propertyValue = new PropertyValue("name", "myBeanUsedBdRegistryPostProcessor");
            mutablePropertyValues.addPropertyValue(propertyValue);
            // 將Bean的屬性值新增到BeanDefinition中
            bd.setPropertyValues(mutablePropertyValues);
            // 註冊該自定義的BeanDefinition,BeanName使用myBeanUsedBdRegistryPostProcessor
            registry.registerBeanDefinition("myBeanUsedBdRegistryPostProcessor", bd);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }
}

然後編寫啟動類,BeanDefinitionRegistryPostProcessorDemo,載入XML配置,從上下文中獲取myBeanUsedBdRegistryPostProcessor名稱的Bean,並執行其printMyName方法:

public class BeanDefinitionRegistryPostProcessorDemo {

    public static void main(String[] args) {
        // 載入配置
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext(
                        "applicationContext-extendpoint/beans.xml");
        // get bean
        MyBeanUsedBdRegistryPostProcessor myBean = context.getBean(
                "myBeanUsedBdRegistryPostProcessor", MyBeanUsedBdRegistryPostProcessor.class);
        // 執行方法
        myBean.printMyName();
    }
}

beans.xml的配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 配置自定義的BeanDefinitionRegistryPostProcessor為Bean -->
    <bean class="com.learn.ioc.extendpoint.process.MyBdRegistryPostProcessor"></bean>
</beans>

方法呼叫執行結果如下:

my name is:myBeanUsedBdRegistryPostProcessor

BeanDefinitionRegistryPostProcessor中自定義的Bean成功的被上下文註冊為單例。當然這裡只是簡單的示例,對於更復雜的需要進行依賴處理。

基於ImportBeanDefinitionRegistrar擴充套件

上節中介紹了ImportBeanDefinitionRegistrar是基於註解的方式BeanDefinition註冊器,允許應用向上下文中註冊更多的BeanDefinition。這裡以筆者專案中的案例作為分析,幫助理解ImportBeanDefinitionRegistrar。

筆者在spring-boot工程的專案中使用了Elastic-Job v1.1.1版本,由於該版本Elastic-Job不支援不支援註解式配置Job Bean,筆者嫌在spring-boot中再引入XML不夠方便和友好,故簡單自己實現了Elastic-Job對註解支援的模組。其中就使用到了Spring提供的ImportBeanDefinitionRegistrar擴充套件點。

原有的Elastic——Job的XML配置主要分為兩大類,第一類是任務註冊中心的配置,第二類是Job相關的配置。其中Job分為多種,每種Job的配置方式不一樣,這裡只實現了對SimpleJob的支援。

首先分析SimpleJob的配置,同Spring Bean的配置差異不大。也是代表Job的標籤,然後就是屬性的配置,再者就是子元素的Bean的配置。如:

<job:simple id="..." class="..."
            registry-center-ref="..."
            overwrite="..."
            cron="..."
            sharding-total-count="..."
            sharding-item-parameters="..."
            monitor-execution="..."
            monitor-port="..."
            failover="..."
            description="...."
            disabled="...">
    <job:listener class="..." started-timeout-milliseconds="..." completed-timeout-milliseconds="..."></job:listener>
</job:simple>

一個job:simple用於定義一個Job的配置,這樣可以抽象一個註解來描述該Job配置,其中子元素job:listener又是屬於這個Job的監聽器子元素配置,同樣也需要抽象出一個註解用於定義該監聽器,如:

/**
 * Elastic-job的Simple型別Job對應的註解
 *
 * @author huaijin
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Component
@Import(ElasticJobRegistrar.class)
public @interface ElasticSimpleJob {

    String id();

    Class<?> classStr();

    boolean overwrite() default true;

    String registryCenterRef();

    String jobParameter() default "";

    String cron();

    String shardingTotalCount();

    String shardingItemParameters() default "";

    boolean jobFailover() default true;

    int monitorPort() default 9880;

    boolean monitorExecution() default false;

    String description() default "";

    String maxTimeDiffSeconds() default "";

    String misfire() default "";

    String jobShardingStrategyClass() default "";

    JobListener jobListener() default @JobListener(startedTimeoutMilliseconds = 0,
            completedTimeoutMilliseconds = 0);

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @interface JobListener {

        Class<?> classStr() default Class.class;

        long startedTimeoutMilliseconds();

        long completedTimeoutMilliseconds();
    }
}

其中該註解被@Component修飾,表示該註解標註的類是一個Spring Bean,能夠被Spring的@Component註解處理檢測載入該類的註解屬性。使用@Import(ElasticJobRegistrar.class)該配置,表示該註解應用被哪個ImportBeanDefinitionRegistrar實現進行處理。

然後就是實現ImportBeanDefinitionRegistrar,用於處理ElasticSimpleJob註解,將其標註的類註冊為Spring中特定型別的BeanDefinition。

/**
 * 解析{@link ElasticSimpleJob},註冊SpringJobScheduler和SimpleJobConfiguration
 *
 * @author huaijin
 */
@Component
public class ElasticJobRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private Environment environment;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {
        // 獲取ElasticSimpleJob註解的屬性集合
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata
                .getAnnotationAttributes(ElasticSimpleJob.class.getName()));
        // 獲取job id屬性
        String id = annoAttrs.getString(BeanDefinitionParserDelegate.ID_ATTRIBUTE);
        // 使用Spring提供的建造者模式構造BeanDefinition,其中型別為SpringJobScheduler
        BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(SpringJobScheduler.class);
        // 設定初始化方法
        factory.setInitMethodName("init");
        // 增加該Bean的第一個構造引數引用,即對註冊中心Bean的引用
        factory.addConstructorArgReference(annoAttrs.getString("registryCenterRef"));
        // 增加該Bean的第二個構造引數引用,對Job配置的引用
        factory.addConstructorArgReference(createJobConfiguration(annoAttrs, registry));
        // 增加第三個構造引數引用,是對job listener的引用
        factory.addConstructorArgValue(createJobListeners(annoAttrs.getAnnotation("jobListener")));
        // 註冊該BeanDefinition
        BeanDefinitionHolder holder = new BeanDefinitionHolder(factory.getBeanDefinition(), id + "SpringJobScheduler");
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }


    private String createJobConfiguration(final AnnotationAttributes annoAttrs, final BeanDefinitionRegistry registry) {
        Class<?> simpleJobConfigurationDto;
        try {
           simpleJobConfigurationDto = Class.forName("com.dangdang.ddframe.job.spring.namespace.parser.simple." +
                   "SimpleJobConfigurationDto");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        factory.addConstructorArgValue(annoAttrs.getString(BeanDefinitionParserDelegate.ID_ATTRIBUTE));
        factory.addConstructorArgValue(annoAttrs.getClass("classStr"));
        factory.addConstructorArgValue(annoAttrs.getString("shardingTotalCount"));
        factory.addConstructorArgValue(annoAttrs.getString("cron"));

        addPropertyValueIfExists(annoAttrs, "shardingItemParameters", factory);
        addPropertyValueIfExists(annoAttrs, "jobParameter", factory);
        addPropertyValueIfExists(annoAttrs, "jobMonitorExecution", factory);
        addPropertyValueIfExists(annoAttrs, "monitorPort", factory);
        addPropertyValueIfExists(annoAttrs, "maxTimeDiffSeconds", factory);
        addPropertyValueIfExists(annoAttrs, "failover", factory);
        addPropertyValueIfExists(annoAttrs, "misfire", factory);
        addPropertyValueIfExists(annoAttrs, "jobShardingStrategyClass", factory);
        addPropertyValueIfExists(annoAttrs, "description", factory);
        String propertyName = "elastic.job.disabled";
        addPropertyValueIfExists(environment, propertyName, factory);
        addPropertyValueIfExists(annoAttrs, "overwrite", factory);
        String result = annoAttrs.getString(BeanDefinitionParserDelegate.ID_ATTRIBUTE) + "Conf";
        registry.registerBeanDefinition(result, factory.getBeanDefinition());
        return result;
    }

    public List<BeanDefinition> createJobListeners(AnnotationAttributes jobListener) {
        List<BeanDefinition> listeners = new ManagedList<>();
        Class<?> listenerClass = jobListener.getClass("classStr");
        if (listenerClass == Class.class) {
            return new ManagedList<>(0);
        }
        BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(listenerClass);
        factory.setScope(BeanDefinition.SCOPE_PROTOTYPE);
        if (AbstractDistributeOnceElasticJobListener.class.isAssignableFrom(listenerClass)) {
            factory.addConstructorArgValue(jobListener.getNumber("startedTimeoutMilliseconds"));
            factory.addConstructorArgValue(jobListener.getNumber("completedTimeoutMilliseconds"));
        }
        listeners.add(factory.getBeanDefinition());
        return listeners;
    }

    protected final void addPropertyValueIfExists(final AnnotationAttributes annoAttrs, final String propertyName,
                                                  final BeanDefinitionBuilder factory) {
        if (annoAttrs.containsKey(propertyName)) {
            Object attributeValue = annoAttrs.get(propertyName);
            if (Objects.nonNull(attributeValue)) {
                factory.addPropertyValue(propertyName, attributeValue.toString());
            }
        }
    }

    protected final void addPropertyValueIfExists(final Environment env, final String propertyName,
                                                  final BeanDefinitionBuilder factory) {
        String propertyValue = env.getProperty(propertyName);
        if (propertyValue != null && !propertyValue.isEmpty()) {
            factory.addPropertyValue("disabled", propertyValue);
        }
    }
}

以上實現利用ImportBeanDefinitionRegistrar擴充套件點,獲取ElasticSimpleJob註解的屬性,然後將其解析填充到相應型別的BeanDefinition中,最後再將BeanDefinition註冊到上下文中。這樣就完成了使用ElasticSimpleJob註解配置Job,並能夠讓Spring正常的載入例項化Job。

基於BeanDefinitionParser擴充套件

BeanDefinitionParser是Spring提供的對XML解析生成BeanDefinition的擴充套件點,應用可以擴充套件該介面,提供自定義XML Tag的解析能力,並生成BeanDefinition註冊至上下文中。

本節將通過定義自定義xsd,編寫自定義的XML配置,編寫BeanDefinitionParser擴充套件實現來展示基於BeanDefinitionParser擴充套件。主要分為以下幾個步驟:

  • 定義應用自身的xsd(XML Schema)
  • 編寫Spring XML配置
  • 編寫BeanDefinitionParser實現
  • 編寫自定義的NameSpaceHandler,其中需要註冊以上實現的BeanDefinitionParser
  • 配置整合BeanDefinitionParser和xsd至Spring中

首先自定義的XML Schema,這裡使用xsd方式(關於dtd,讀者可以自行研究)。如下:

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.huaijin.com/schema/MyBean"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://www.huaijin.com/schema/MyBean"
            elementFormDefault="qualified">

    <xsd:element name="MyBean">
        <xsd:complexType>
            <xsd:attribute name="id" type="xsd:string" use="required"></xsd:attribute>
            <xsd:attribute name="name" type="xsd:string"></xsd:attribute>
            <xsd:attribute name="class" type="xsd:string" use="required"></xsd:attribute>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

該xsd自定義了XML Tag MyBean的描述。MyBean有三個基本屬性id,name,class。

然後再使用自定義的XML Tag定義Bean:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:Mybean="http://www.huaijin.com/schema/MyBean"
       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
                           http://www.huaijin.com/schema/MyBean http://www.huaijin.com/schema/MyBean/MyBean.xsd">
     <!-- 利用自定義的XML Tag定義Bean -->
    <Mybean:MyBean id="myHelloService" class="com.learn.ioc.bean.parser.extend.MyHelloService"></Mybean:MyBean>
</beans>

再編寫BeanDefinitionParser實現:

/**
 * 自定義Bean定義解析器
 *
 * @author huaijin
 */
public class MyBeanBeanDefinitionParser implements BeanDefinitionParser {

    private static final String TAG_ID = "id";
    private static final String TAG_CLASS = "class";

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        // 獲取id屬性
        String id = element.getAttribute(TAG_ID);
        // 獲取class屬性
        String classType = element.getAttribute(TAG_CLASS);
        // 校驗id和class屬性
        if (id == null || id.isEmpty()) {
            throw new BeanDefinitionParsingException(new Problem("id must be not null.",
                    new Location(parserContext.getReaderContext().getResource())));
        }
        if (classType == null || classType.isEmpty()) {
            throw new BeanDefinitionParsingException(new Problem("classType must be not null.",
                    new Location(parserContext.getReaderContext().getResource())));
        }
        // 使用class建立BeanDefintion
        BeanDefinition beanDefinition;
        try {
            beanDefinition = BeanDefinitionReaderUtils.createBeanDefinition(null, classType,
                    parserContext.getReaderContext().getBeanClassLoader());
        } catch (ClassNotFoundException e) {
            throw new BeanDefinitionParsingException(new Problem("classType can't exist.",
                    new Location(parserContext.getReaderContext().getResource())));
        }
        // 使用id作為BeanName註冊該BeanDefinition至上下文中
        BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(beanDefinition, id);
        BeanDefinitionReaderUtils.registerBeanDefinition(beanDefinitionHolder, parserContext.getRegistry());
        return beanDefinition;
    }
}

然後便是編寫NameSpaceHandler,註冊以上的BeanDefinitionParser:

/**
 * 自定義擴張的名稱空間解析器
 *
 * @author huaijin
 */
public class MyBeanNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        // 註冊BeanDefinitionParser
        registerBeanDefinitionParser("MyBean", new MyBeanBeanDefinitionParser());
    }
}

最後再配置xsd和自定義的BeanDefinitionParser至Spring中。這個過程需要在resource目錄下配置兩個檔案/META-INF/spring.handlers和/META-INF/spring.schemas。
其中spring.handlers中定義名稱空間和xsd檔案位置的對映,使得Spring能夠根據名稱空間找xsd檔案方便對XML配置進行格式校驗;
spring.schemas中定義名稱空間和NameSpaceHandler的對映,使得Spring在處理XML名稱空間時能夠獲取具體的NameSpaceHandler,通過其獲得註冊的BeanDefinitionParser針對性處理該名稱空間的XML配置。

spring.handlers中配置如下:

http\://www.huaijin.com/schema/MyBean=com.learn.ioc.bean.parser.extend.MyBeanNamespaceHandler

spring.schemas中配置如下:

http\://www.huaijin.com/schema/MyBean/MyBean.xsd=com/learn/ioc/bean/parser/extend/MyBean.xsd

最後再編寫測試主類,從上下文中後去該自定義的配置的Bean,並呼叫方法執行驗證

/**
 * 自定義擴充套件解析器Demo
 *
 * @author huaijin
 */
public class ExtendBeanDefinitionParserDemo {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("/bean.parser-extend/extend-parser.xml");
        MyHelloService myHelloService = context.getBean("myHelloService", MyHelloService.class);
        myHelloService.sayMyHello();
    }
}

執行結果如下:

hello, you!

到這裡,關於BeanDefinition的擴充套件點實戰基本都詳細介紹結束,其中關於各種方式都詳細編碼,如果需要了解更多詳情,可以參考Spring官網對各種方式的描述。下節將從原始碼實現的角度分析這幾種方式的擴充套件原理。

BeanDefinition擴充套件點的原理

本節針對以上的四種方式的擴充套件點原理展開介紹,關於BeanFactoryPostProcessor的原理在前文中已經介紹,這裡不再贅述。關於BeanDefinitionRegistryPostProcessor的原理在BeanFactoryPostProcessor一文的原始碼分析中也有涉獵,即在Spring上下文建立完內部的BeanFactory,載入BeanDefinition後,在例項化和喚醒BeanFactoryPostProcessor的邏輯前,預留了BeanDefinitionRegistryPostProcessor的擴充套件,允許應用向BeanFactory中註冊更多BeanDefinition,以背後續的BeanFactoryPostProcessor進行後置處理。

同時需要注意的是BeanDefinitionRegistryPostProcessor本身也是BeanFactoryProcessor的擴充套件抽象:

// 繼承BeanFactoryProcessor
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {

    // 該擴充套件點提供了BeanDefinitionRegistry,利用其可以向上下文中註冊BeanDefinition
    // 同時也能修改同時也能修改BeanDefinitionRegistry的屬性
    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}

關於ImportBeanDefinitionRegistrar的原理,其中ImportBeanDefinitionRegistrar主要是Spring在處理@Configurer註解時的擴充套件點,需要了解Spring註解配置處理原理的基礎,才能夠清晰的理解,故本文中不做詳細介紹,待後續文章中介紹Spring註解配置原理中再細說ImportBeanDefinitionRegistrar的原理。

本節主要針對BeanDefinitionParser的原理實現做詳細介紹。

為了更好的講解BeanDefinitionParser,這裡先總結下幾個與其相關的重要元件:

  • DefaultBeanDefinitionDocumentReader
  • BeanDefinitionParserDelegate
  • DefaultNamespaceHandlerResolver
  • NameSpaceHandler

DefaultBeanDefinitionDocumentReader和BeanDefinitionParserDelegate在前面的Spring原始碼系列 — BeanDefinition文章有過原始碼程度的分析。前者主要負責讀取Document文件中的BeanDefinition配置,後者負責解析配置並負責委託處理其他的名稱空間配置的解析。

DefaultNamespaceHandlerResolver是用於解析名稱空間處理器,它主要提供根據XML名稱空間解析NameSpaceHandler的能力。

NameSpaceHandler提供兩個能力,其一是能夠註冊BeanDefinitionParser和XML Tag的對映關係;其二提供根據XML Tag尋找BeanDefinitionParser。

總結下,即DefaultNamespaceHandlerResolver包含XML名稱空間和NameSpaceHandler的對映關係,NameSpaceHandler中包含XML Tag和BeanDefinitionParser的對映關係。

觸發BeanDefinitionParser XML Tag的流程如下:

接下來就從原始碼的角度分析下這個流程。仍然回到DefaultBeanDefinitionDocumentReader中parseBeanDefinitions方法:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // 判斷XML根元素是否為預設Beans名稱空間,如果是則按照預設方式解析
    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;
                // 判斷子元素是否為預設的Beans名稱空間,如果是則解析Beans
                if (delegate.isDefaultNamespace(ele)) {
                    parseDefaultElement(ele, delegate);
                }
                else {
                    // 如果不是,則認為是自定義的
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        // 如果不是,則認為是自定義的
        delegate.parseCustomElement(root);
    }
}

對於非Beans名稱空間而言,主要進入delegate.parseCustomElement分支,解析自定義的XML Tag。

再來詳細看parseCustomElement實現:

 // 解析BeanDefinition
 public BeanDefinition parseCustomElement(Element ele) {
     return parseCustomElement(ele, null);
 }
 public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
     // 獲取該Element對應的名稱空間,利用了Java XML提供的介面
     String namespaceUri = getNamespaceURI(ele);
     // 根據名稱空間獲取NameSpaceHandler
     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解析Element
     return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
 }

其中主要就是根據名稱空間獲取NameSpaceHandler,然後利用handler解析XML ELemnent為BeanDefinition。主要關注NameSpaceHandler的獲取過程:

@Override
public NamespaceHandler resolve(String namespaceUri) {
    // 獲取名稱空間和NameSpaceHandler的對映關係
    Map<String, Object> handlerMappings = getHandlerMappings();
    // 根據名稱空間獲取NameSpaceHandler
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    // 如果為空,則返回null
    if (handlerOrClassName == null) {
        return null;
    }
    // 如果直接是NameSpaceHandler的例項,則直接返回
    else if (handlerOrClassName instanceof NamespaceHandler) {
        return (NamespaceHandler) handlerOrClassName;
    }
    // 否則認為是NameSpaceHandler實現的類名
    else {
        // 轉化為類名
        String className = (String) handlerOrClassName;
        try {
            // 獲取對應的Class物件
            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");
            }
            // 根據Class物件,建立NameSpaceHandler例項
            NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
            // 執行初始化方法
            namespaceHandler.init();
            // 覆蓋原有的對映關係,快取作用
            handlerMappings.put(namespaceUri, namespaceHandler);
            // 返回NameSpaceHandler
            return namespaceHandler;
        }
        catch (ClassNotFoundException ex) {
            throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
                    namespaceUri + "] not found", ex);
        }
        catch (LinkageError err) {
            throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
                    namespaceUri + "]: problem with handler class file or dependent class", err);
        }
    }
}

以上的邏輯也非常簡單,首先獲取名稱空間和NameSpaceHandler的對映關係,然後根據名稱空間獲取相應的NameSpaceHandler。這裡主要需要關注的是如何獲取名稱空間和NameSpaceHandler的對映關係:

private Map<String, Object> getHandlerMappings() {
    Map<String, Object> handlerMappings = this.handlerMappings;
    // 如果handlerMappings不為空,則直接返回,否則載入handlerMappings
    if (handlerMappings == null) {
        // 對handlerMappings的修改有資料競態,同步
        synchronized (this) {
            // 雙重鎖定檢查,如果仍然為空,則載入handlerMappings
            handlerMappings = this.handlerMappings;
            if (handlerMappings == null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
                }
                try {
                    // 根據handlerMappingsLocation指定的文章,使用工具載入properties
                    Properties mappings =
                            PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Loaded NamespaceHandler mappings: " + mappings);
                    }
                    // 將properties轉為ConcurrentHashMap
                    handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
                    CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                    this.handlerMappings = handlerMappings;
                }
                catch (IOException ex) {
                    throw new IllegalStateException(
                            "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
                }
            }
        }
    }
    return handlerMappings;
}

需要注意的是,這裡Spring使用了約定配置的做法,對於獲取對映關係配置,是由Spring框架內建和應用擴充套件的。在spring中定義了預設的配置檔案位置:

public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";

即在類路徑下的META-INF/spring.handlers中配置。這是spring約定。所以上節的案例中也配置該檔案。同時在spring的其他模組,如:beans、context、aop中都有該配置檔案。

beans模組中配置如下:

http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler

context模組中配置如下:

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

aop模組配置如下:

http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

DefaultNameSpaceHandlerResovler中中持有名稱空間和名稱空間處理器的對映關係。在獲取到相應的名稱空間處理器後,需要進行初始化。初始化的過程就是註冊BeanDefinitionParser的過程,該過程主要是建立XML Tag與BeanDefinitionParser的之間的對映關係。如上節的案例中,建立了"MyBean"的Tag和MyBeanDefinitionParser之間的關係。這裡以ContextNamespaceHandler為例,講解其init方法的細節:

public class ContextNamespaceHandler extends NamespaceHandlerSupport {

    // 初始化,註冊BeanDefinitionParser,建立XML Tag與BeanDefinitionParser之間的關係
    @Override
    public void init() {
        // 註冊PropertyPlaceholderBeanDefinitionParser,讓其解析Tag:"property-placeholder"
        registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
        // 註冊PropertyOverrideBeanDefinitionParser,讓其解析Tag:"property-override"
        registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
        // 註冊AnnotationConfigBeanDefinitionParser,讓其解析Tag:"annotation-config"
        registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
        // 註冊ComponentScanBeanDefinitionParser,讓其解析Tag:"ComponentScanBeanDefinitionParser"
        registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
        // 註冊LoadTimeWeaverBeanDefinitionParser,讓其解析Tag:"load-time-weaver"
        registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
        // 註冊SpringConfiguredBeanDefinitionParser,讓其解析Tag:"spring-configured"
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
        // 註冊MBeanExportBeanDefinitionParser,讓其解析Tag:"mbean-export"
        registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
        // 註冊MBeanServerBeanDefinitionParser,讓其解析Tag:"mbean-server"
        registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
    }
}

從以上ContextNameSpaceHandelr中可以看出Context名稱空間下的各個XML Tag所對應的BeanDefinitionParser是什麼。比如常用的component:scan標籤由ComponentScanBeanDefinitionParser負責解析。關於這些BeanDefinitonParser的實現細節,將在下篇Spring中註解處理中挑一些詳細介紹,這裡不再詳述。

再繼續看handler.parse的實現,其中主要是根據元素的Tag尋找對應的BeanDefinitionParser,然後解析XML Element為對應的BeanDefinition。仍然以ContextNameSpaceHandler為例介紹:

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
    // 查詢BeanDefinitionParser,然後解析Element
    return findParserForElement(element, parserContext).parse(element, parserContext);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    // 獲取Element的Tag
    String localName = parserContext.getDelegate().getLocalName(element);
    // 根據Tag獲取BeanDefinitionParser
    BeanDefinitionParser parser = this.parsers.get(localName);
    if (parser == null) {
        parserContext.getReaderContext().fatal(
                "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }
    // 返回Parser
    return parser;
}

在NameSpaceHandler中是利用Map儲存Tag與BeanDefinitionParser之間的對映關係的。

到這裡,應該能從頭至尾非常清楚的瞭解了BeanDefinitionParser支撐應用自定義擴充套件XML Tag解析BeanDefintion的原理了。

總結

本文主要介紹了Spring中BeanDefition中處理的擴充套件點。主要從擴充套件點的方式、實戰案例、原理三個方面層層深入介紹。

參考

Extensible XML authoring