服务层的作用就是封装复杂业务逻辑,尤其是这种逻辑涉及到多个Domain Class的时候。
我们在前面的文章中已经看到,实际上在Controller中就可以畅通无阻的使用Domain Class,那为什么还要搞出来一个单独的服务层呢?原因很简单,职责清晰。从架构上讲,Controller仍属于Web层的范畴,它的主要作用还是为了View的显示做好准备工作,或是针对请求准备好响应的数据。而业务逻辑则属于业务层的内容,两者混在一起,不仅让Controller变得复杂,而且也为测试造成了麻烦。基于这种原因,一般建议:不要在Controller中包含核心业务逻辑,凡是涉及多个Domain Class的操作,就封装成Service,由Controller调用。
服务的创建非常简单,使用命令:grails create-service,即可。和其他的Grails组件一样,服务也有自己的存放位置:grails-app/services。当然,服务本身也同样是普通的Groovy类。
服务要和多个Domain Class打交道,这当然就离不开事务了。关于编程性事务的内容,我们已经在GORM部分讲过,在此就不再啰嗦。现在来看看声明性事务是如何定义的,这也很简单,只要在服务类中写上:static transactional = true就行了:
class CountryService {
    static transactional = true
}
特点:
  • 服务缺省使用声明性事务,将其设为false即关闭
  • 声明性事务只有在DI方式才能工作,使用new方式将无效。
  • 缺省的级别是PROPAGATION_REQUIRED
Grails也完全支持Spring的事务注解:
import org.springframework.transaction.annotation.*
class BookService {
 @Transactional(readOnly = true) 
 def listBooks() { Book.list() }
 
 @Transactional
 def updateBook() { }
}
在使用事务注解时,无需任何配置,只需使用即可,Grails会负责把这些东西自动串起来。
用过Spring的读者应该对Spring的scope不会陌生,它决定了bean的创建方式和生命周期,跟并发性不无联系。一般状况,服务是Singleton,scope的值包括:
  • prototype:新实例/注入时
  • request:新实例/request
  • flash:新实例/flash
  • flow:新实例/flow
  • conversation:新实例/conversation
  • session:新实例/session
  • singleton(缺省):自始至终一个实例
改变缺省的Scope(在服务定义中)增加:static scope = "以上之一"。
至于DI,Grails遵守的约定适用于Controller、Service、Domain Class、Command Object、Tag等:
class BookController {
   def bookService //对应BookService
   …
}
需要注意:
  • 按名字,不支持按类型
  • 在声明服务时,不建议使用强类型,因为当类型变化时,reload时DI会出现问题。
除了一般的Groovy类,我们同样可以在Java类中使用服务:
  • 方法1:使用带包名的Service
  • 方法2:定义接口;Service实现接口;
不论以上哪种方式,在Java端:
  • 代码:src/java
    package bookstore;
        public class BookConsumer {
         private BookStore store; 
         public void setBookStore(BookStore storeInstance) {
          this.store = storeInstance;
         } 
         …
        }
  • bean定义:grails-app/conf/spring/resources.xml
    <bean id="bookConsumer" class="bookstore.BookConsumer">
         <property name="bookStore" ref="bookService" />
        </bean>