12.5 使用MarkupBuilder生成XML
Groovy类MarkupBuilder是一种使用Groovy动态编程功能创建的类。前一章介绍了动态地拦截方法调用,在Java和Kotlin等静态语言中,这种功能是无法如此天衣无缝地实现的。
为了演示这种功能,下面是一个使用Groovy类MarkupBuilder的示例。你无需在Eclipse IDE中输入这些代码,但可在GroovyConsole中输入并运行它们:
def xmlContent = new StringWriter()def xmlWriter = new groovy.xml.MarkupBuilder(xmlContent)xmlWriter.items {item(id: 1) {name("Item one")}item(id: 2) {name("Item two")}}println(xmlContent)
这些代码所做的工作很多,但我们先来看看结果,再详细介绍。这些代码将向控制台打印如下输出:
<items><item id='1'><name>Item one</name></item><item id='2'><name>Item two</name></item></items>
MarkupBuilder类的构造函数将一个实现了Java接口Writer的对象作为参数,生成的数据将写入到这个对象中。我们可以使用一个FileWriter实例将输出存储到一个文本文件中,但在这里,我们需要的是一个String,因此传入了一个StringWriter实例。
别忘了,在Groovy中调用函数时,括号是可选的。代码行xmlWriter.items { … }也可写成下面这样:
xmlWriter.items({...})
在这个示例中,显然是调用了方法items(),它将一个闭包作为输入参数。MarkupBuilder类没有方法items(),但它拦截未知的方法调用和属性访问,并根据使用的方法名和参数创建XML元素。
根据SQL查询结果生成XML
知道如何使用MarkupBuilder后,就可以编写根据博文记录生成XML的方法了。这个方法比较复杂,我们将分步完成其编写工作。首先来定义方法generateXML(),并添加生成XML的变量。为此,在Main类中添加如下代码:
def generateXML() {def xmlContent = new StringWriter()def xmlWriter = new groovy.xml.MarkupBuilder(xmlContent)}
接下来定义同时获取博文数据和用户名的SQL查询。为此,在方法generateXML()的末尾添加如下代码:
def connection = createDatabaseConnection()def sql = new Sql(connection)def sqlQuery = """SELECT B.id, B.title, B.post, U.name AS user_nameFROM blog BINNER JOIN user U ON B.user = U.id"""sql.eachRow(sqlQuery) { record ->}
在任何情况下,在一个查询中获取尽可能多的信息都是不错的主意,因为这比依次执行多个查询的速度更快。在这里,我们使用INNER JOIN子句将数据表user和blog连接起来。这个查询返回如下列:
- blog.id;
- blog.title;
- blog.post;
- user.name。
在这个查询中,我们给user.name列指定了别名,这样可在代码中使用别名user_name来表示用户名。
我们使用Sql类的方法eachRow()来遍历返回的记录,它借鉴了函数式编程范式的做法,让我们无需手动编写循环来遍历记录,而只需指定一个将对每条返回的记录调用的闭包函数。这个闭包的代码可使用参数record来读取每天记录的值。在这个闭包中,我们将为每条记录创建一个XML条目。为此请在其中添加如下代码:
xmlWriter.posts {post(id: record.id) {title(record.title)user(record.user_name)def p = record.postpost(p.getSubString(1, p.length().intValue()))}}
这段代码你应该非常熟悉。我们创建了一个根节点为的XML。为了读取记录,只需将SQL查询中的列名(或别名,如user_name)视为属性名即可,唯一的例外是post字段。前面创建数据表时,post字段的类型被指定为CLOB,之所以这样做是因为我们不知道博文将包含多少行文本。类型为CLOB的字段的长度是不确定的,因此要读取它,必须指定每次要读取多少个字符。鉴于我们预期博文比内存量小得多,因此我们调用CLOB字段post的函数length()来指定每次要读取的字符数,从而一次性读取整个博文。
在生产级应用程序中,最好对每次读取的字符数进行限制,以避免字段消耗过多的服务器内存。为此,一种办法是小批量地读取并处理字段中的数据。
最后,我们需要关闭数据库连接,并让方法generateXML()以普通Java字符串的方式返回生成的XML。为此,在这个方法的末尾添加如下代码:
def generateXML() {def xmlContent = new StringWriter()def xmlWriter = new groovy.xml.MarkupBuilder(xmlContent)...sql.close()return xmlContent.toString()}
在方法main()中的代码行connection.close()后面,添加调用方法generateXML()的代码:
...app.openDatabaseConnection()app.createDatabaseStructure()app.addDemoRecords()connection.close()println(app.generateXML())...
你可能会问,为何要在方法中新建数据库连接,而不重用变量connection呢?这将在后面修改这个应用程序的实现,将其变成Web服务时做出解释。
如果现在运行这个应用程序,将看到如下输出:
<posts><post id='1'><title>Test post</title><user>Admin</user><post>This is a test post</post></post></posts>

