1. 概述
本教程将介绍Smooks框架的核心功能。我们将解析其设计理念、关键特性,并演示如何使用其高级功能。首先简单说明下这个框架的定位。
2. Smooks框架
Smooks是处理结构化数据(如XML/CSV)的专用框架。它提供API和配置模型,支持预定义格式间的转换(如XML转CSV、XML转JSON等)。我们可以使用FreeMarker或Groovy脚本等工具实现映射。除转换外,Smooks还支持消息验证、数据拆分等特性。
2.1 核心特性
Smooks的主要应用场景包括:
- ✅ 消息转换:多源格式到多输出格式的数据转换
- ✅ 消息增强:从数据库等外部源填充额外数据
- ✅ 数据拆分:处理GB级大文件并拆分为小文件
- ✅ Java绑定:从消息构建并填充Java对象
- ✅ 消息验证:执行正则验证或自定义规则
3. 初始配置
在pom.xml
中添加Maven依赖:
<dependency>
<groupId>org.milyn</groupId>
<artifactId>milyn-smooks-all</artifactId>
<version>1.7.0</version>
</dependency>
最新版本可在Maven Central获取。
4. Java绑定
需要扩展配置映射,添加supplier
和item
的bean定义。注意我们定义了独立的items
bean来存储所有item
元素(使用ArrayList)。最后通过Smooks的wiring
属性整合所有组件:
<?xml version="1.0"?>
<smooks-resource-list
xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.2.xsd">
<jb:bean beanId="order"
class="com.baeldung.smooks.model.Order" createOnElement="order">
<jb:value property="number" data="order/order-number" />
<jb:value property="status" data="order/order-status" />
<jb:value property="creationDate"
data="order/@creation-date" decoder="Date">
<jb:decodeParam name="format">yyyy-MM-dd</jb:decodeParam>
</jb:value>
<jb:wiring property="supplier" beanIdRef="supplier" />
<jb:wiring property="items" beanIdRef="items" />
</jb:bean>
<jb:bean beanId="supplier"
class="com.baeldung.smooks.model.Supplier" createOnElement="supplier">
<jb:value property="name" data="name" />
<jb:value property="phoneNumber" data="phone" />
</jb:bean>
<jb:bean beanId="items"
class="java.util.ArrayList" createOnElement="order">
<jb:wiring beanIdRef="item" />
</jb:bean>
<jb:bean beanId="item"
class="com.baeldung.smooks.model.Item" createOnElement="item">
<jb:value property="code" data="item/code" />
<jb:value property="price" decoder="Double" data="item/price" />
<jb:value property="quantity" decoder="Integer" data="item/quantity" />
</jb:bean>
</smooks-resource-list>
在测试中添加断言验证:
assertThat(
order.getSupplier(),
is(new Supplier("Company X", "1234567")));
assertThat(order.getItems(), containsInAnyOrder(
new Item("PX1234", 9.99,1),
new Item("RX990", 120.32,1)));
5. 消息验证
Smooks提供基于规则的验证机制。规则定义存储在配置文件的ruleBases
标签中,可包含多个ruleBase
元素。每个ruleBase
需指定:
name
:唯一标识名src
:规则源文件路径provider
:实现RuleProvider
接口的全限定类名
内置两种提供者:RegexProvider
(正则验证)和MVELProvider
(复杂逻辑验证)。
5.1 RegexProvider
使用RegexProvider
验证客户名称和电话号码格式。源文件需为Java属性文件,以键值对形式存储正则表达式:
supplierName=[A-Za-z0-9]*
supplierPhone=^[0-9\\-\\+]{9,15}$
5.2 MVELProvider
用MVELProvider
验证订单总价是否低于200。源文件采用CSV格式,包含规则名和MVEL表达式两列:
"max_total","orderItem.quantity * orderItem.price < 200.00"
5.3 验证配置
准备好规则源文件后,在Smooks配置中添加验证标签:
executeOn
:验证元素路径name
:引用的ruleBase
onFail
:验证失败时的处理方式
完整配置示例(注意MVELProvider
需配合Java绑定使用):
<?xml version="1.0"?>
<smooks-resource-list
xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
xmlns:rules="http://www.milyn.org/xsd/smooks/rules-1.0.xsd"
xmlns:validation="http://www.milyn.org/xsd/smooks/validation-1.0.xsd">
<import file="smooks-mapping.xml" />
<rules:ruleBases>
<rules:ruleBase
name="supplierValidation"
src="supplier.properties"
provider="org.milyn.rules.regex.RegexProvider"/>
<rules:ruleBase
name="itemsValidation"
src="item-rules.csv"
provider="org.milyn.rules.mvel.MVELProvider"/>
</rules:ruleBases>
<validation:rule
executeOn="supplier/name"
name="supplierValidation.supplierName" onFail="ERROR"/>
<validation:rule
executeOn="supplier/phone"
name="supplierValidation.supplierPhone" onFail="ERROR"/>
<validation:rule
executeOn="order-items/item"
name="itemsValidation.max_total" onFail="ERROR"/>
</smooks-resource-list>
测试供应商电话号码验证失败场景:
public ValidationResult validate(String path)
throws IOException, SAXException {
Smooks smooks = new Smooks(OrderValidator.class
.getResourceAsStream("/smooks/smooks-validation.xml"));
try {
StringResult xmlResult = new StringResult();
JavaResult javaResult = new JavaResult();
ValidationResult validationResult = new ValidationResult();
smooks.filterSource(new StreamSource(OrderValidator.class
.getResourceAsStream(path)), xmlResult, javaResult, validationResult);
return validationResult;
} finally {
smooks.close();
}
}
断言验证错误:
@Test
public void givenIncorrectOrderXML_whenValidate_thenExpectValidationErrors() throws Exception {
OrderValidator orderValidator = new OrderValidator();
ValidationResult validationResult = orderValidator
.validate("/smooks/order.xml");
assertThat(validationResult.getErrors(), hasSize(1));
assertThat(
validationResult.getErrors().get(0).getFailRuleResult().getRuleName(),
is("supplierPhone"));
}
6. 消息转换
Smooks的消息转换也称为模板化,支持:
- FreeMarker(推荐方案)
- XSL
- String模板
使用FreeMarker将XML转换为类EDIFACT格式,并生成邮件模板:
EDIFACT模板:
UNA:+.? '
UNH+${order.number}+${order.status}+${order.creationDate?date}'
CTA+${supplier.name}+${supplier.phoneNumber}'
<#list items as item>
LIN+${item.quantity}+${item.code}+${item.price}'
</#list>
邮件模板:
Hi,
Order number #${order.number} created on ${order.creationDate?date} is currently in ${order.status} status.
Consider contacting the supplier "${supplier.name}" with phone number: "${supplier.phoneNumber}".
Order items:
<#list items as item>
${item.quantity} X ${item.code} (total price ${item.price * item.quantity})
</#list>
基础配置(需导入Java绑定配置):
<?xml version="1.0"?>
<smooks-resource-list
xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
xmlns:ftl="http://www.milyn.org/xsd/smooks/freemarker-1.1.xsd">
<import file="smooks-validation.xml" />
<ftl:freemarker applyOnElement="#document">
<ftl:template>/path/to/template.ftl</ftl:template>
</ftl:freemarker>
</smooks-resource-list>
只需传递StringResult
给Smooks引擎:
Smooks smooks = new Smooks(config);
StringResult stringResult = new StringResult();
smooks.filterSource(new StreamSource(OrderConverter.class
.getResourceAsStream(path)), stringResult);
return stringResult.toString();
测试转换结果:
@Test
public void givenOrderXML_whenApplyEDITemplate_thenConvertedToEDIFACT()
throws Exception {
OrderConverter orderConverter = new OrderConverter();
String edifact = orderConverter.convertOrderXMLtoEDIFACT(
"/smooks/order.xml");
assertThat(edifact,is(EDIFACT_MESSAGE));
}
7. 总结
本教程介绍了如何使用Smooks进行消息格式转换、Java对象绑定、正则/业务规则验证等操作。⚠️ 需注意:复杂规则验证时务必先配置Java绑定。踩坑提示:FreeMarker模板路径需使用绝对路径,相对路径可能导致加载失败。
所有示例代码可在GitHub仓库获取。