• 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

  • 相关阅读:
    软工作业02
    进行代码复审训练
    源代码管理工具调查
    软工作业PSP与单元测试训练【任务一】
    15软工 15100340
    进行代码复审训练
    源代码管理工具调查
    软工作业PSP与单元测试训练
    软工课后作业001
    20180320作业2:进行代码复审训练
  • 原文地址:https://www.cnblogs.com/lxyit/p/10160782.html
Copyright © 2020-2023  润新知