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绑定

需要扩展配置映射,添加supplieritem的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仓库获取。


原始标题:Introduction to Smooks