本文的主要内容是对 Grails 1.1 中 PetClinic-MVC 实例的代码分析。这个实例主要验证了如何在非 Grails 的项目中使用 GORM。此外也演示了 Spring MVC 2.5 的一些用法。本文假设大家对 Spring、Spring MVC、Groovy 和 Grails 有着初步的了解。OK, let's go!
Domain 层
目前 Grails 只提供了在普通 Spring 应用中独立使用 GORM 的方法(现在 Groovy 和 Grails 已是 SpringSource 旗下的产品了,自然要首先对自家产品提供支持)。不过相信以后在其它 Java EE 框架中也可独立使用 GORM。
在 Grails 以外使用 GORM,只需在 Domain 类上使用 grails.persistence.Entity 注释即可。其它的就像在 Grails 中的一样。
在配置方面也很简单,只要使用 <gorm:sessionFactory ... /> 即可。详细请见 Grails 的 PetClinic,这里就不拗述了。
Controller 层
同 Domain 层一样,Controller 层也全部使用 Groovy 实现。主要是因为 GORM 的大部分方法都是动态的。
1. ClinicController: ClinicController 实现了 InitializingBean 接口。这个接口只有一个方法 afterPropertiesSet。这个方法会在 Bean 完成属性设置之后执行。PetClinic 用这个方式实现了类似 Grails 中 BootStrap 的部分功能。在这个方法中我们可以看到 GORM 中的 save() 方法和 addTo* 动态方法都是可用的。
可能有人会问,afterPropertiesSet() 这个方法的作用就是增加一些预定义的数据,为什么不使用构造函数来实现。实际上,在本例中,afterPropertiesSet() 的作用的确可以用构造函数来实现(经过测试)。至于作者是出于什么目的而选择了实现 InitializingBean 接口我也说不好。
ClinicController 的其它部分就是一些简单的不能再简单的 Http 请求处理函数了。需要说明的一点是 @RequestMapping 中的值代表了请求的 URL,这个是不用带后缀的,也就是说 ".do" 是多余的。
2. AddPetForm:
在 AddPetForm 类的级别上有三个注释,分别是 @Controller, @RequestMapping 和 @SessionAttributes。@Controller 不用多说,作用是这个类声明为一个 Spring Controller,并添加到 Context 中。下面介绍一下 @RequestMapping 和 @SessionAttributes 的功能:
- @RequestMapping 可以定义处理请求的路径和方法类型,即可放在类声明上,也可放在方法声明上。在 AddPetForm 中,类和方法上都有 @RequestMapping,类上的 @RequestMapping 用来定义请求路径。两个 @RequestMapping 的方法分别负责处理 get 请求和 post 请求。但我认为更好的方式是在一个 Controller 类中定义多个处理请求的方法,就像 Grails 的 Controller 那样。需要注意的是方法上面的 @RequestMapping 上面的值并不会覆盖类上的 @RequestMapping 中的值。
- @SessionAttributes 中的值代表了 org.springframework.ui.Model 中应该被设置为 Session 范围的变量。在 Spring MVC 应用中,这些变量往往是起到 ActionForm(Struts) 或 CommandObject(Spring MVC) 的作用。之所以要将表单对象放在 Session 中,是为了使其能够在表单显示和之后的表单提交这样过程中不丢失。@SessionAttributes 的这种用法是源于 Spring MVC 2.0 AbstractFormController 的 SessionForm 模式。
实际上,使用 @SessionAttributes 是很成问题的,有时是不可接受的。考虑以下场景:在 PetClinic 应用中,新建两个 Owner,然后为这两个 Owner 都添加新的 Pet。注意,只要显示出来 Add Pet 的页面即可。这时,这两个页面的 URL 应该是 addPet.do?ownerId=num1 和 addPet.do?ownerId=num2 。这时将先打开的表单 提交 ,假设是 num1 的那个。你会发现你 实际上是为 num2 Owner 添加了一个 Pet。
这个问题的原因其实是很简单的,我就不解释了。从上面这个事实来看,@SessionAttributes 的缺点还是比较明显。是否采用这种方式还要看这个问题对你的应用的影响是否是致命的了。Spring MVC 有必要改进一下,像 Seam 那样可以很方便的使用 Conversation 是解决问题的一个好办法。(当然也可以使用 hidden 域来保存 id)。
Spring MVC 之所以要使用这种有较明显缺陷的方式的原因是要使用 Spring MVC 的表单数据绑定功能。由于目前还没有找到更好的方式,所以只能使用 @SessionAttribute 了。这一块还是 Spring MVC 的一个短板,需要在新版本中改进。
除此以外,AddPetForm 中还有两个特殊的方法,一个是注释了 @ModelAttribute 的方法;另一个是注释了 @InitBinder 的方法。下面分别介绍一下:
- @ModelAttribute 可以用在方法上,也可以用在形参上。如果放在方法上,那它会在进入请求处理方法是准备一些数据,作用和 SimpleFormController 的 referenceData() 一样。@ModelAttribute 的这种用法可能会让人联想到 Seam 中的 @Factory。但被 @ModelAttribute 注释的方法只会在同一个 Controller 中被注释了 @RequestMapping 的方法调用之前被调用,而 @Factory 方法则是全局的。
你可以在 setupForm 方法中向 request 或 session 中设置一些需要显示在页面上的数据,但是如果只放在 request 里的话,在表单验证发生时,数据便会丢失;放在 session 缺点自不必多说。在 Spring Web Flow 新加入的 flow 和 conversation 等新的 scope 无法很容易地在 Spring MVC 中使用的情况下,使用 @ModelAttribute 无疑是更好的选择。并且使用 @ModelAttribute 可以在多个 @RequestMapping 方法之间实现代码的重用。 - @InitBinder 适用于注册只属于本 Controller 的属性编辑器。但在 AddPetForm 中,@InitBinder 方法只是告诉 Spring MVC,路径为(<form: input path="id" />) id 的值不要使用属性编辑器。不过页面里也压根没有 路径为 id 的值。所以,我觉得这个又是一个多余的代码。(我试过将 @InitBinder 方法注释掉,对程序没有影响)
Controller 层中两个特殊的类:ClinicBindingInitializer 和 PetTypeEditor
- ClinicBindingInitializer 的作用是注册全句的属性编辑器
- PetTypeEditor 是用于处理 PetType 类型数据绑定的属性编辑器。对于像 PetType 这样的实体之间的关联属性,恰当地使用属性编辑器可以简化程序的开发。 (不知 Grails 是否实现了一个动态的属性编辑器,用于关联属性的数据绑定)
配置
1. applicationContext.xml
applicationContext 中大部分都是常见的 Spring 配置。特殊一点的配置除了 <gorm:sessionFactory ... /> 以外,还有 Hibernate StatisticsService JMX 的配置。
2. dispatcher-servlet.xml
Spring MVC 配置文件(也是一个 Spring 的配置文件)的默认名是 dispatcher-servlet.xml。
从上至下,第一个配置表明哪个 Package 下的 Class 使用了 Spring 的 @Controller 注释。这样 Spring 容器在启动的时候会自动扫描其下的 Class,将加注了 @Controller 注释的类加入到 Spring 的容器中。
第二个配置 DefaultAnnotationHandlerMapping 是 HandlerMapping 接口的一个最常用的实现类。用于映射 Handler 和请求路径之间的关系。通过这样的配置,Controller 中的 @RequestMapping 中的路径值就可以真正地和 Http 请求对应起来。HandlerMapping 的另外一个常用的实现类是 ControllerClassNameHandlerMapping 。
第三个配置 AnnotationMethodHandlerAdapter 的作用是使处理请求的 Handler 能够真正的具有处理请求的能力而提供一些服务,包括为请求参数绑定应用相应的属性编辑器(通过注册 webBindingInitializer 实现)。
==========================结束的分割线==========================
写到这里,PetClinic-MVC 中值得分析学习的代码都提到了。PetClinic-MVC 是一个很小的应用,但是其中演示了 GORM 在普通 Spring 中的应用和 Spring MVC 2.5 的使用。细看的话还是能学到不少东西的。我所写的只是我的理解,难免会有一些不足和错误,欢迎纠正。