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.附件源码
下载源码,希望对你有所帮助。