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环境中通过`component-scan`配置来处理自定义Java...
以下是对Spring中自定义Schema解析生效的详细解释: 首先,自定义Schema的基本流程是通过在XML配置文件中引入自定义的schemaLocation,这通常是一个URL,指向定义了新标签和属性的XSD文件。例如,Spring Data ...
总的来说,这个“扩展Spring schema样例代码 maven”项目提供了一个实践平台,帮助开发者了解并掌握如何在Spring中创建自定义schema以及在Maven项目中进行整合。通过学习和分析这个项目,你可以加深对Spring框架的...
2. **模块化**:自定义Schema可以帮助将特定功能封装成独立的模块,减少核心配置的复杂度。 3. **灵活性**:通过自定义Schema,可以轻松添加新的配置选项,适应项目的演进需求。 总结来说,Spring自定义配置Schema...
标签“源码”暗示了内容可能深入到Spring框架的内部实现,可能涉及解析XML Schema的源代码,比如`DefaultListableBeanFactory`和`BeanDefinitionReader`等类的作用,以及它们如何将XML配置转化为实际的Bean实例。...
本教程将详细讲解如何实现这一过程,通过创建自定义Schema和处理注解,使得Spring能够自动发布基于注解的WebService服务。 首先,创建一个新的Java项目,并引入必要的依赖。如文中所述,可以使用Maven来创建...
2. **实现解析器(Parser Class)**:解析器通常是实现了`org.springframework.beans.factory.xml.BeanDefinitionParser`接口的类,负责解析自定义标签并生成`BeanDefinition`。例如,`...
`BeanToJsonSchema`项目正是为了解决这个问题,它提供了一个功能,能够将Java Bean对象转换成对应的JSON Schema,以便于在JSON数据交换和验证中使用。 JSON Schema的核心特性包括但不限于: 1. **数据类型**:JSON...
开发者需要创建一个自定义的`NamespaceHandler`,并实现`parse`方法来解析自定义标签,将解析结果转化为`BeanDefinition`对象并注册到Spring的IoC容器中。为了使Spring知道如何找到对应的`NamespaceHandler`,还需要...
本篇文章将深入探讨Spring自定义标签的定义、解析以及相关源码分析。 首先,自定义标签的定义通常涉及到两个主要步骤: 1. **定义XSD文件**: XSD(XML Schema Definition)文件用于描述XML文档的结构和数据类型...
基于Spring开发之自定义标签及其解析 Spring框架是现在Java最流行的开源框架之一,需要实现一些自定义的标签,主要是方便使用我们框架的人能够快速、简单进行配置。自定义标签的实现离不开XML Schema的定义,我们...
6. **后处理**:Spring提供了扩展点,如BeanPostProcessor,允许在bean实例化后和初始化前执行自定义逻辑。 7. **单例管理**:对于单例bean,BeanFactory会缓存它们的实例,确保每次请求时返回的是同一个bean。 ...
接下来,我们需要在Spring的主配置文件中引入这个自定义schema,这样Spring才能识别我们定义的标签: ```xml <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi=...
在Spring中自定义XSD,开发者可以创建自己的扩展,以便在XML配置文件中定义特定的bean或行为。这个过程对于那些希望对Spring配置进行模块化或者定制化的项目尤其有用。下面将详细解释如何进行Spring自定义XSD。 ...
通过实现`org.springframework.beans.factory.xml.NamespaceHandler`接口,我们可以定义自己的处理逻辑,将自定义标签转换为Spring能够理解的Bean定义。 `spring-web-namespacehandler`通常包含Spring MVC相关的...
9. **tool**: 工具模块提供了如BeanDefinitionReader、BeanDefinitionParser等工具,用于读取和解析bean定义,通常在开发自定义扩展或集成时会用到。 10. **jee**: Java EE(Java Platform, Enterprise Edition)...
接下来,我们需要在Spring的`schemaLocation`中声明这个自定义标签的命名空间和解析器。在XML配置文件的头部添加以下内容: ```xml <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi=...
XML扩展是Spring框架早期的主要配置方式,允许开发者自定义标签来增强其功能。在本篇文章中,我们将深入探讨Spring XML扩展以及自定义标签技术。 首先,了解Spring XML配置的基本结构至关重要。在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 ...