`
游伯度
  • 浏览: 22010 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

自定义 Schema 解析 Spring Bean

阅读更多

1.写在前面的废话

Spring 2.0 以后 Spring 就支持了客户端自定义 Schema 来表示 Bean 定义。同时也提供了通用的支持类来帮助完成复杂的解析工作。至于他的优点,个人感觉是当需要复杂的构造对象时,自定义 Schema 来定义 Bean 是有优势的。他在可读性,可以配置性可以有很大的提高。至于其他的,需要读者去体会了。

网上有很多这样的例子,当时有很多我感觉过于简单,和实际项目中用到的东西有一点儿脱节。下面就用日常项目中可能使用到任务处理方式来说明自定义 Schema 来定义自己的 Bean 定义是如何来实现的,当然也是简化之后的版本。

 

2. 任务处理的需求以及一般处理逻辑和处理方式

常见的任务场景是当某一事件完成后,异步的处理的任务触发,可能是一个,也可能是多个。例如:当车从车库开出后,车库门需要关闭,车库的灯需要关掉。

对应上面场景,我们可以分解一下详细的描述:

  • 当车开出车库时,释放一个信号,就发送一条消息,告知汽车已经离开的车库了
  • 消息的关注者可以处理这个消息。例如车库灯关注这个消息,他创建一条关灯的任务,然后在规定时间内把灯关掉;车库门关注这个消息,他创建一条关闭车库门的任务,然后在规定的时间内把车库门关闭。
  • 任务的执行。上一步中关注者创建了任务,下面就可以让任务调度器来扫描任务,执行任务。例如没秒扫描一次,判断是否需要执行关闭车库门,是否需要关闭车库灯。

 

3. 自定义 schema 做自己的 Spring Bean 定义的步骤

3.1整体部署描述

自定义 schema,解析 Bean 定义整体流程是下面几步:

  • 自定义 schema,表述 Bean 定义需要遵循的配置规范
  • 定义自己的命名空间处理器(NamespaceHandler
  • 定义自己的设定的 schema 相关的 Bean 解析器(BeanDefinitionParser
  • 配置 spring.handlers,让 Spring 容器知道对应的命名空间的处理器
  • 配置 spring.schemas,让 Spring 容器知道对应解析 document 的检查依据
  • 实现自己的业务
  • 配置 Bean 定义文件,串联所有的业务

3.2具体事例

3.2.1根据上述的场景,进行业务实现方式抽象

  • EventSource:事件源,把车库当成事件的载体,当汽车开出车库时,触发释放车离开车库的事件。同时他应该具体注册监听事件的监听者的能力,使各个监听者可以顺利的注册到时间源上。当某种事件发生后,通知对应的监听者。
  • EventListener:监听者,监听事件,例如车库门是一个监听者,他监听到车开出车库的消息时,进行自己的处理。这里是创建一条车库门关闭的任务。
  • Task:任务,描述的任务本身的信息,例如任务的标识,任务的类型,执行计划,执行状态等。例如关闭车库门的场景:任务标识可自动生成,任务类型是车库门关闭,执行计划是车开出车库1分钟后关闭车库门,执行状态是等待执行。
  • TaskProcessor:任务处理器,真实的处理过程。例如关闭车库门的操作。

3.2.2自定义 schema 描述

 

定义一个事件的描述,他包含了一个事件源,多个事件监听者,和多个事件监听的执行器。位于 classpath:META-INF 下面的 event.xsd

<?xml version="1.0"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://www.lh.experiment.com/schema/event"
            xmlns="http://www.lh.experiment.com/schema/event"
            elementFormDefault="qualified">
    <xsd:element name="event">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element name="source" type="EventSourceType" minOccurs="1" maxOccurs="1"/>
                <xsd:element name="listener" type="EventListenerType" minOccurs="0" maxOccurs="unbounded"/>
                <xsd:element name="task" type="TaskType" minOccurs="0" maxOccurs="unbounded" />
            </xsd:sequence>
            <xsd:attribute name="id" type="xsd:string" use="required" />
        </xsd:complexType>
    </xsd:element>

    <xsd:complexType name="EventSourceType">
        <xsd:attribute name="value" type="xsd:string" use="required" />
    </xsd:complexType>

    <xsd:complexType name="EventListenerType">
        <xsd:attribute name="id" type="xsd:string" use="required" />
        <xsd:attribute name="concern" type="xsd:string" use="required" />
        <xsd:attribute name="value" type="xsd:string" use="required" />
    </xsd:complexType>

    <xsd:complexType name="TaskType">
        <xsd:all>
            <xsd:element name="type" type="DefaultRequiredValueType" />
            <xsd:element name="processor" type="DefaultRequiredValueType" />
        </xsd:all>
        <xsd:attribute name="id" type="xsd:string" use="required" />
    </xsd:complexType>

    <xsd:complexType name="DefaultRequiredValueType">
        <xsd:attribute name="value" type="xsd:string" use="required" />
    </xsd:complexType>

</xsd:schema>

 

3.2.3命名空间处理器

 

定义支持“event”命名空间的处理器。一般情况下我们选择 spring 提供的支持类即可,但是如果你愿意,你也可以实现原始的 NamespaceHandler 接口,完全实现。这里我们选择继承org.springframework.beans.factory.xml.NamespaceHandlerSupport完成。

public class EventNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        super.registerBeanDefinitionParser("event", new EventBeanDefinitionParser());
    }
}

 

3.2.4 Bean 定义解析器

 

在做命名空间处理器时,需要我们指定 Bean 定义解析器。同样我们可以选择 Spring 提供支持类作为基类来实现。只要实现 BeanDefinitionPaser 就好。这个解析类承载了真正的 Bean 解析工作:对各个节点、元素、属性的解析,已经需要生成对应的 BeanDefinitionHolder,注册到容器中。

public class EventBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
    private static final Set<String> CHILD_NODE_SET = new HashSet<String>(Arrays.asList(
            "source", "listener", "task"
    ));

    @Override
    protected Class<?> getBeanClass(Element element) {
        return EventMetadata.class;
    }

    @Override
    protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
        MutablePropertyValues propertyValues = builder.getBeanDefinition().getPropertyValues();

        String source = parseSourceNode(element);
        propertyValues.addPropertyValue("source", new RuntimeBeanReference(source));

        ManagedList listenerList = new ManagedList();
        ManagedList taskList = new ManagedList();
        NodeList childNodes = element.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node child = childNodes.item(i);
            String nodeName = child.getLocalName();
            if (!CHILD_NODE_SET.contains(nodeName)) continue;

            if ("listener".equals(nodeName)) {
                parseListenerNode((Element) child, source, listenerList);
            }
            else if ("task".equals(nodeName)) {
                parseTaskNode((Element) child, taskList);
            }
        }
        if (!listenerList.isEmpty()) {
            propertyValues.addPropertyValue("listenerMetadata", listenerList);
        }
        if (!taskList.isEmpty()) {
            propertyValues.addPropertyValue("taskMetadata", taskList);
        }
    }

    private String parseSourceNode(Element element) {
        NodeList childNodes = element.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node child = childNodes.item(i);
            String nodeName = child.getLocalName();
            if ("source".equals(nodeName)) {
                return ((Element) child).getAttribute("value");
            }
        }
        return null;
    }

    private void parseListenerNode(Element listenerNode, String sourceName, ManagedList listenerList) {
        String id = listenerNode.getAttribute("id");
        String concern = listenerNode.getAttribute("concern");
        String listenerName = listenerNode.getAttribute("value");

        BeanDefinitionBuilder listenerBdb = BeanDefinitionBuilder.genericBeanDefinition();
        listenerBdb.getRawBeanDefinition().setBeanClass(ListenerMetadata.class);
        MutablePropertyValues propertyValues = listenerBdb.getBeanDefinition().getPropertyValues();
        propertyValues.addPropertyValue("concernId", concern);
        propertyValues.addPropertyValue("listener", new RuntimeBeanReference(listenerName));
        propertyValues.addPropertyValue("source", new RuntimeBeanReference(sourceName));
        BeanDefinitionHolder holder = new BeanDefinitionHolder(listenerBdb.getBeanDefinition(), id);
        listenerList.add(holder);
    }

    private void parseTaskNode(Element taskNode, ManagedList taskList) {
        BeanDefinitionBuilder taskBdb = BeanDefinitionBuilder.genericBeanDefinition();
        taskBdb.getRawBeanDefinition().setBeanClass(TaskMetadata.class);

        MutablePropertyValues propertyValues = taskBdb.getBeanDefinition().getPropertyValues();

        String id = taskNode.getAttribute("id");
        NodeList childNodes = taskNode.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node child = childNodes.item(i);
            String nodeName = child.getLocalName();
            if ("type".equals(nodeName)) {
                String taskType = ((Element)child).getAttribute("value");
                propertyValues.addPropertyValue("taskType", taskType);
            }
            else if ("processor".equals(nodeName)) {
                String taskProcessor = ((Element)child).getAttribute("value");
                propertyValues.addPropertyValue("taskProcessor", new RuntimeBeanReference(taskProcessor));
            }
        }
        propertyValues.addPropertyValue("taskSPI", new RuntimeBeanReference("taskSPI"));

        BeanDefinitionHolder holder = new BeanDefinitionHolder(taskBdb.getBeanDefinition(), id);
        taskList.add(holder);
    }
}

 

3.2.5  spring.handlers 配置

 

自己定义在类路径 META-INF/spring.handlers 文件,写下如下配置:

http\://www.lh.experiment.com/schema/event=com.lh.a.t.spring.ioc.framework.EventNamespaceHandler

 

 

3.2.6  spring.schemas 配置

 

自定义在类路径 META-INF/spring.schemas 文件,写下如下配置:

http\://www.lh.experiment.com/schema/event/event.xsd=META-INF/event.xsd

 

3.2.7  Bean 配置,串联业务

业务代码实现这里不做说明,可以查看源码在附件中,

 

下面我们配置我们需要 bean 定义文件,我定义在类路径 spring/spring-bean-schema.xml 中,配置如下:

<?xml version="1.0" encoding="GBK"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:event="http://www.lh.experiment.com/schema/event"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
       http://www.lh.experiment.com/schema/event http://www.lh.experiment.com/schema/event/event.xsd"
        default-autowire="byName">

    <event:event id="garageEventConfig">
        <event:source value="garage"/>

        <event:listener id="lightOffListener" concern="car-drive-off" value="lightOffProcessor"/>
        <event:listener id="gateCloseListener" concern="car-drive-off" value="gateCloseProcessor"/>

        <event:task id="lightOffTask">
            <event:type value="task-light-off"/>
            <event:processor value="lightOffProcessor"/>
        </event:task>
        <event:task id="gateCloseTask">
            <event:type value="task-gate-close"/>
            <event:processor value="gateCloseProcessor"/>
        </event:task>
    </event:event>

    <bean id="garage" class="com.lh.a.t.spring.ioc.biz.support.Garage"/>
    <bean id="lightOffProcessor" class="com.lh.a.t.spring.ioc.biz.support.LightOffProcessor"/>
    <bean id="gateCloseProcessor" class="com.lh.a.t.spring.ioc.biz.support.GateCloseProcessor"/>
    <bean id="taskSPI" class="com.lh.a.t.spring.ioc.biz.support.TaskServiceProvider"/>

</beans>

 

3.2.8测试执行

 

写一个简单的测试类来运行一下:

public class GarageIocTest {
    private static ApplicationContext applicationContext;

    private Garage garage;
    private TaskSPI taskSPI;

    @BeforeClass
    public static void beforeClass() {
        applicationContext = new ClassPathXmlApplicationContext(new String[] {
                "spring/spring-bean-schema.xml"
        });
    }

    @Before
    public void before() {
        garage = (Garage) applicationContext.getBean("garage");
        taskSPI = (TaskSPI) applicationContext.getBean("taskSPI");
    }

    @Test
    public void testCarDriveOff() throws Exception {
        garage.carDriveOff();

        List<Task> executableTaskList = mockExecutableList();
        for (Task executableTask : executableTaskList) {
            new Thread(new TaskRunnable(taskSPI, executableTask)).start();
        }
    }

    private List<Task> mockExecutableList() {
        List<Task> executableTaskList = new ArrayList<Task>();

        Task executableLightOffTask = new Task();
        executableLightOffTask.setTaskType(Task.TaskType.LIGHT_OFF.get());
        executableLightOffTask.setTaskId("light-off-0001");
        executableTaskList.add(executableLightOffTask);

        Task executableGateCloseTask = new Task();
        executableGateCloseTask.setTaskType(Task.TaskType.GATE_CLOSE.get());
        executableGateCloseTask.setTaskId("gate-close-0001");
        executableTaskList.add(executableGateCloseTask);

        return executableTaskList;
    }

    private class TaskRunnable implements Runnable {
        private TaskSPI taskSPI;
        private Task executableTask;

        private TaskRunnable(TaskSPI taskSPI, Task executableTask) {
            this.taskSPI = taskSPI;
            this.executableTask = executableTask;
        }

        @Override
        public void run() {
            StringBuilder context = new StringBuilder();
            context.append(executableTask.getTaskId()).append("@").append(executableTask.getTaskType());
            taskSPI.execute(executableTask, context.toString());
        }
    }
}

 

运行结果如下:

2014-10-11 22:40:24,490 main [GateCloseProcessor.java:18] INFO  : Gate close processor incept message:Car No A99999 get out the garage

2014-10-11 22:40:24,491 main [LightOffProcessor.java:18] INFO  : Light off processor incept message:Car No A99999 get out the garage

 

2014-10-11 22:40:24,493 Thread-0 [LightOffProcessor.java:29] INFO  : Light off processor execute, taskId:light-off-0001, context:light-off-0001@task-light-off

 

4. 总结

Spring 自定义schema 方式支持客户自定义 Bean 定义的方式,在可读性上有很大的提高。同时代码的方式我们可以为我们的业务量身定做配置,可以简化配置工作,同时根据业务来验证约束我们配置,减少配置出错的几率,在一定程度上可以提高开发效率。

 

 

5.附件源码

下载源码,希望对你有所帮助。

分享到:
评论

相关推荐

    Spring中自定义Schema如何解析生效详解

    Spring2.5在2.0的基于Schema的...通过该机制,我们可以编写自己的Schema,并根据自定义的Schema用自定的标签配置Bean,下面这篇文章主要介绍了关于Spring中自定义Schema如何解析生效的相关资料,需要的朋友可以参考下

    Spring-Reference_zh_CN(Spring中文参考手册)

    6.3. Schema-based AOP support 6.3.1. 声明一个切面 6.3.2. 声明一个切入点 6.3.3. 声明通知 6.3.3.1. 通知(Advice) 6.3.3.2. 返回后通知(After returning advice) 6.3.3.3. 抛出异常后通知(After throwing ...

    Spring 2.0 开发参考手册

    6.3. Schema-based AOP support 6.3.1. 声明一个切面 6.3.2. 声明一个切入点 6.3.3. 声明通知 6.3.4. 引入 6.3.5. 切面实例化模型 6.3.6. Advisors 6.3.7. 例子 6.4. AOP声明风格的选择 6.4.1. Spring AOP...

    Spring中文帮助文档

    6.3. 基于Schema的AOP支持 6.3.1. 声明一个切面 6.3.2. 声明一个切入点 6.3.3. 声明通知 6.3.4. 引入 6.3.5. 切面实例化模型 6.3.6. Advisor 6.3.7. 例子 6.4. AOP声明风格的选择 6.4.1. Spring AOP还是...

    spring chm文档

    6.3. Schema-based AOP support 6.3.1. 声明一个切面 6.3.2. 声明一个切入点 6.3.3. 声明通知 6.3.4. 引入 6.3.5. 切面实例化模型 6.3.6. Advisors 6.3.7. 例子 6.4. AOP声明风格的选择 6.4.1. Spring AOP...

    Spring API

    6.3. 基于Schema的AOP支持 6.3.1. 声明一个切面 6.3.2. 声明一个切入点 6.3.3. 声明通知 6.3.4. 引入 6.3.5. 切面实例化模型 6.3.6. Advisor 6.3.7. 例子 6.4. AOP声明风格的选择 6.4.1. Spring AOP还是...

    Spring攻略(第二版 中文高清版).part1

    1.8 使用工厂Bean和Utility Schema定义集合 29 1.8.1 问题 29 1.8.2 解决方案 29 1.8.3 工作原理 29 1.9 用依赖检查属性 31 1.9.1 问题 31 1.9.2 解决方案 32 1.9.3 工作原理 32 1.10 用@Required...

    Spring攻略(第二版 中文高清版).part2

    1.8 使用工厂Bean和Utility Schema定义集合 29 1.8.1 问题 29 1.8.2 解决方案 29 1.8.3 工作原理 29 1.9 用依赖检查属性 31 1.9.1 问题 31 1.9.2 解决方案 32 1.9.3 工作原理 32 1.10 用@Required...

    Spring.3.x企业应用开发实战(完整版).part2

    10.4.1 Spring通过单实例化Bean简化多线程问题 10.4.2 启动独立线程调用事务方法 10.5 联合军种作战的混乱 10.5.1 Spring事务管理器的应对 10.5.2 Hibernate+Spring JDBC混合框架的事务管理 10.6 特殊方法成漏网之鱼...

    Spring3.x企业应用开发实战(完整版) part1

    10.4.1 Spring通过单实例化Bean简化多线程问题 10.4.2 启动独立线程调用事务方法 10.5 联合军种作战的混乱 10.5.1 Spring事务管理器的应对 10.5.2 Hibernate+Spring JDBC混合框架的事务管理 10.6 特殊方法成漏网之鱼...

    webx3框架指南PDF教程附学习Demo

    • 扩展性 —— Webx 3.0对Spring做了扩展,使Spring Bean不再是“bean”,而是升级成“组件”。一个组件可以扩展另一个组件,也可以被其它组件扩展。这种机制造就了Webx的非常好的扩展性,且比未经扩展的Spring更易...

    Java语言基础下载

    Struts ActionForm Bean捕获表单数据 648 ActionForm的处理流程 649 Struts的其他组件 652 内容总结 653 独立实践 653 第三十二章:配置Struts组件 654 学习目标 654 三个 XML文件和一个属性文件 655 Web应用部署...

    Java/JavaEE 学习笔记

    第四章 XML Schema.....................115 第五章 XML解析...................119 Oracle学习笔记...............121 前言....................................121 第一章 Selecting Rows........................

    J2EE学习笔记(J2ee初学者必备手册)

    第四章 XML Schema.....................115 第五章 XML解析.119 Oracle学习笔记...............121 前言....121 第一章 Selecting Rows.....................124 第二章 Limiting Selected Rows.......127 第三章 ...

Global site tag (gtag.js) - Google Analytics