自定义 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.附件源码

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

猜你喜欢

转载自bodu-li.iteye.com/blog/2145400