ObjectGraphBuilder是一个生成器(builder),它可以用来生成符合JavaBean协议的bean的任意的图,它非常有用,譬如可以用来创建测试数据。
比如说你的领域模型包含了下面的类:
package com.acme class Company { String name Address address List employees = [] } class Address { String line1 String line2 int zip String state } class Employee { String name int employeeId Address address Company company }
通过ObjectGraphBuilder生成一个有3个Employee的Company就这么容易:
def builder = new ObjectGraphBuilder() builder.classNameResolver = "com.acme" def acme = builder.company( name: 'ACME' ){ 3.times { employee( id: it, name: 'Drone ${it}' ) } } assertNotNull acme assert acme.employees.size() == 3
下面描述了在后台发生了什么事情:
所有这4个策略都有一个默认的实现,如果代码符合编写JavaBeans的普遍的协议的话,那么这些实现都会如你期望的运行。但是,如果万一你的bean没有符合协议,那么你可以使用你自己对各个策略的实现。每个策略的设置器(setter)都是闭包友好的,例如
builder.newInstanceResolver = { klass, attributes -> if( attributes.foo ){ return klass.newInstance( [attributes.foo] as Object[] ) } // 默认无参构造函数 klass.newInstance() }
ObjectGraphBuilder支持为每个节点提供id,就像SwingBuilder那样,这意味着你可以在生成器中“存储”一个节点的引用,这对于把一个实例关联到其他多个实例来说非常有用。因为一个名为“id”的属性在一些领域模型中可能有业务含义,所以ObjectGraphBuilder包含了一个名为IdentifierResolver的策略,你可以设置它来改变默认的名称(“id”)。相同的情况可能会出现在另外一个属性上,这个属性用来引用一个预先保存的实例,一个名为ReferenceResolver的策略可以用来设置适当的值(默认为“refId”):
def company = builder.company( name: 'ACME' ) { address( id: 'a1', line1: '123 Groovy Rd', zip: 12345, state: 'JV' ) employee( name: 'Duke', employeeId: 1, address: a1 ) }
def company = builder.company( name: 'ACME' ) { address( id: 'a1', line1: '123 Groovy Rd', zip: 12345, state: 'JV' ) employee( name: 'Duke', employeeId: 1 ){ address( refId: 'a1' ) } }
值得指出的一点是,你不可以修改一个被引用的bean的属性。
在ObjectGraphBuilder无法定位你的类(当你使用groovyConsole来运行一个脚本的时候就会发生)的这种极少出现的情况下,你可以为ObjectGraphBuilder定义一个classLoader来解析类。例如,尝试在groovyConsole中运行下面的代码,然后把classLoader属性注释掉。
class Conference { String name List speakers = [] } class Speaker { String name } def ogb = new ObjectGraphBuilder( classLoader: getClass().classLoader ) def j1 = ogb.conference( name: 'JavaOne') { speaker( name: 'Duke' ) } assert j1.speakers.size() == 1 assert j1.speakers[0].name == 'Duke'