服務層的作用就是封裝複雜業務邏輯,尤其是這種邏輯涉及到多個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>