`
游伯度
  • 浏览: 22749 次
  • 性别: 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 自定义注解的解析

    在Spring框架中,自定义注解的解析是一个强大的特性,允许开发者根据业务需求创建特定的注解,并在Spring容器启动时自动处理这些注解。本文将深入探讨如何在Spring环境中通过`component-scan`配置来处理自定义Java...

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

    以下是对Spring中自定义Schema解析生效的详细解释: 首先,自定义Schema的基本流程是通过在XML配置文件中引入自定义的schemaLocation,这通常是一个URL,指向定义了新标签和属性的XSD文件。例如,Spring Data ...

    扩展Spring schema样例代码 maven

    总的来说,这个“扩展Spring schema样例代码 maven”项目提供了一个实践平台,帮助开发者了解并掌握如何在Spring中创建自定义schema以及在Maven项目中进行整合。通过学习和分析这个项目,你可以加深对Spring框架的...

    Spring自定义配置Schema可扩展(二)

    2. **模块化**:自定义Schema可以帮助将特定功能封装成独立的模块,减少核心配置的复杂度。 3. **灵活性**:通过自定义Schema,可以轻松添加新的配置选项,适应项目的演进需求。 总结来说,Spring自定义配置Schema...

    spring schema

    标签“源码”暗示了内容可能深入到Spring框架的内部实现,可能涉及解析XML Schema的源代码,比如`DefaultListableBeanFactory`和`BeanDefinitionReader`等类的作用,以及它们如何将XML配置转化为实际的Bean实例。...

    Spring自定义配置Schema可扩展(一)

    本教程将详细讲解如何实现这一过程,通过创建自定义Schema和处理注解,使得Spring能够自动发布基于注解的WebService服务。 首先,创建一个新的Java项目,并引入必要的依赖。如文中所述,可以使用Maven来创建...

    spring 自定义xml标签

    2. **实现解析器(Parser Class)**:解析器通常是实现了`org.springframework.beans.factory.xml.BeanDefinitionParser`接口的类,负责解析自定义标签并生成`BeanDefinition`。例如,`...

    BeanToJsonSchema:Java bean转换为Json Schema

    `BeanToJsonSchema`项目正是为了解决这个问题,它提供了一个功能,能够将Java Bean对象转换成对应的JSON Schema,以便于在JSON数据交换和验证中使用。 JSON Schema的核心特性包括但不限于: 1. **数据类型**:JSON...

    这一次搞懂Spring自定义标签以及注解解析原理说明.docx

    开发者需要创建一个自定义的`NamespaceHandler`,并实现`parse`方法来解析自定义标签,将解析结果转化为`BeanDefinition`对象并注册到Spring的IoC容器中。为了使Spring知道如何找到对应的`NamespaceHandler`,还需要...

    Spring源码解密之自定义标签与解析

    本篇文章将深入探讨Spring自定义标签的定义、解析以及相关源码分析。 首先,自定义标签的定义通常涉及到两个主要步骤: 1. **定义XSD文件**: XSD(XML Schema Definition)文件用于描述XML文档的结构和数据类型...

    基于Spring开发之自定义标签及其解析

    基于Spring开发之自定义标签及其解析 Spring框架是现在Java最流行的开源框架之一,需要实现一些自定义的标签,主要是方便使用我们框架的人能够快速、简单进行配置。自定义标签的实现离不开XML Schema的定义,我们...

    spring中的BeanFactory解析xml文件

    6. **后处理**:Spring提供了扩展点,如BeanPostProcessor,允许在bean实例化后和初始化前执行自定义逻辑。 7. **单例管理**:对于单例bean,BeanFactory会缓存它们的实例,确保每次请求时返回的是同一个bean。 ...

    spring自定义标签

    接下来,我们需要在Spring的主配置文件中引入这个自定义schema,这样Spring才能识别我们定义的标签: ```xml &lt;beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi=...

    spring 自定义xsd

    在Spring中自定义XSD,开发者可以创建自己的扩展,以便在XML配置文件中定义特定的bean或行为。这个过程对于那些希望对Spring配置进行模块化或者定制化的项目尤其有用。下面将详细解释如何进行Spring自定义XSD。 ...

    spring-扩展点-namespacehandler(Spring自定义标签)

    通过实现`org.springframework.beans.factory.xml.NamespaceHandler`接口,我们可以定义自己的处理逻辑,将自定义标签转换为Spring能够理解的Bean定义。 `spring-web-namespacehandler`通常包含Spring MVC相关的...

    spring-5.2.19.RELEASE-schema.zip

    9. **tool**: 工具模块提供了如BeanDefinitionReader、BeanDefinitionParser等工具,用于读取和解析bean定义,通常在开发自定义扩展或集成时会用到。 10. **jee**: Java EE(Java Platform, Enterprise Edition)...

    自定义xml标签

    接下来,我们需要在Spring的`schemaLocation`中声明这个自定义标签的命名空间和解析器。在XML配置文件的头部添加以下内容: ```xml &lt;beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi=...

    spring xml扩展

    XML扩展是Spring框架早期的主要配置方式,允许开发者自定义标签来增强其功能。在本篇文章中,我们将深入探讨Spring XML扩展以及自定义标签技术。 首先,了解Spring XML配置的基本结构至关重要。在Spring应用中,...

    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 ...

Global site tag (gtag.js) - Google Analytics