Grails陷阱之一

最近在摆弄Grails,感觉Grails还是很好用的,不过还是遇到一些小问题。因为是基于groovy这样的动态语言,因此许多问题是要到运行时才抛出异常,而有些时候所抛出的异常并不能反应问题的实质,结果往往令人摸不到头脑,从而构成了陷阱(gotchas)。我也经过了多次这样的抓狂经历。

[b]Grails陷阱之一:Domain Class 中的关联不能指向抽象类[/b]

Grails的domain class可以继承抽象类。比如:

abstract class Pet {
String name
boolean eat(Food food) {
println("$name is eating ${food.toString()}...")
}
}
class Cat extends Pet {
}
class Dog extends Pet {
}

abstract class Food {
}
class Fish extends Food {
String toString() { 'a fish' }
}
class Bone extends Food {
String toString() { 'a bone' }
}

class Person {

String name

boolean feed(Pet pet, Food food) {
pet.eat(food)
}
}

/**********************************************************/

import grails.test.*

class FeedPetTests extends GrailsUnitTestCase {
protected void setUp() {
super.setUp()
}

protected void tearDown() {
super.tearDown()
}

void testFeed() {
def hax = new Person(name:'Hax')
def tom = new Cat(name:'Tom')
def snoopy = new Dog(name:'Snoopy')
def fish = new Fish()
def bone = new Bone()
hax.feed(tom, fish)
hax.feed(tom, bone)
hax.feed(snoopy, fish)
hax.feed(snoopy, bone)
}
}


上述代码可以编译和运行成功。

--Output from testFeed--
Tom is eating a fish...
Tom is eating a bone...
Snoopy is eating a fish...
Snoopy is eating a bone...


如果我们给Person加上一个pets关联的话:


class Person {

static hasMany = [pets:Pet] // 关联到抽象类Pet

String name

boolean feed(Pet pet, Food food) {
pet.eat(food)
}
}


结果可以编译,但是运行时出现异常:


gant.TargetExecutionException: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'messageSource': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager': Cannot resolve reference to bean 'sessionFactory' while setting bean property 'sessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory': Invocation of init method failed; nested exception is java.lang.NullPointerException

at gant.Gant$_dispatch_closure4.doCall(Gant.groovy:331)
at gant.Gant$_dispatch_closure6.doCall(Gant.groovy:334)
at gant.Gant$_dispatch_closure6.doCall(Gant.groovy)
at gant.Gant.withBuildListeners(Gant.groovy:344)
at gant.Gant.this$2$withBuildListeners(Gant.groovy)
at gant.Gant$this$2$withBuildListeners.callCurrent(Unknown Source)
at gant.Gant.dispatch(Gant.groovy:334)
at gant.Gant.this$2$dispatch(Gant.groovy)
at gant.Gant.invokeMethod(Gant.groovy)
at gant.Gant.processTargets(Gant.groovy:495)
at gant.Gant.processTargets(Gant.groovy:480)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'messageSource': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager': Cannot resolve reference to bean 'sessionFactory' while setting bean property 'sessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory': Invocation of init method failed; nested exception is java.lang.NullPointerException
at java.security.AccessController.doPrivileged(Native Method)
at _GrailsBootstrap_groovy$_run_closure2_closure13.doCall(_GrailsBootstrap_groovy:86)
at _GrailsBootstrap_groovy$_run_closure2_closure13.doCall(_GrailsBootstrap_groovy)
at _GrailsSettings_groovy$_run_closure10.doCall(_GrailsSettings_groovy:269)
at _GrailsBootstrap_groovy$_run_closure2.doCall(_GrailsBootstrap_groovy:84)
at _GrailsBootstrap_groovy$_run_closure7.doCall(_GrailsBootstrap_groovy:142)
at _GrailsTest_groovy$_run_closure7.doCall(_GrailsTest_groovy:238)
at _GrailsTest_groovy$_run_closure7.doCall(_GrailsTest_groovy)
at _GrailsTest_groovy$_run_closure1_closure19.doCall(_GrailsTest_groovy:106)
at _GrailsTest_groovy$_run_closure1.doCall(_GrailsTest_groovy:92)
at TestApp$_run_closure1.doCall(TestApp.groovy:66)
at gant.Gant$_dispatch_closure4.doCall(Gant.groovy:324)
... 10 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager': Cannot resolve reference to bean 'sessionFactory' while setting bean property 'sessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory': Invocation of init method failed; nested exception is java.lang.NullPointerException
at java.security.AccessController.doPrivileged(Native Method)
... 22 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory': Invocation of init method failed; nested exception is java.lang.NullPointerException

at java.security.AccessController.doPrivileged(Native Method)
... 23 more
Caused by: java.lang.NullPointerException
... 24 more


光看这些异常你肯定丈二和尚摸不着头脑。我就在这个上栽了跟头,浪费了好多时间去定位问题根源。

问题本身其实不难理解,涉及到数据库关联的,不管是一对一、一对多或多对多,都要指向一个实际的数据库表,而grails是不会为抽象类建立对应的数据库表的,也就是说,在grails中[b]抽象类本身并不是domain class,而只是提供了一种代码复用的手段[/b](也就是节约了一点重复性代码,减少了一些C&P)。

解决方法很简单,就是把Pet抽象类改为具体类,或者修改你的关联,不要关联到抽象类,而是直接关联到具体类。

类似的,如果你关联了一个interface,也会抛出差不多的异常,不过在/grails-app/domain下放置interface会编译失败,你必须放到/src/groovy下,而所以一般来说你不太会遇到interface的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值