領先一步
VMware 提供培訓和認證,助力您快速進步。
瞭解更多其中包括許多簡化與資料繫結相關的複雜而繁瑣問題的功能。一般來說,Grails 透過提供多種將資料對映繫結到物件圖的技術,使得資料繫結變得非常簡單。
應用程式開發者理解每種技術的含義至關重要,以便決定哪種技術最適合特定用例且最安全。
考慮一個看起來像這樣的域類
class Employee {
String firstName
String lastName
BigDecimal salary
}
應用程式中可能有一個表單允許更新 firstName 和 lastName 屬性。該表單可能不允許更新 salary 屬性,該屬性可能只能由應用程式的其他部分更新。
用於更新特定員工的控制器動作可能看起來像這樣
class EmployeeController {
def updateEmployee() {
// retrieve the employee from the database
def employee = Employee.get(params.id)
// update properties in the employee
employee.firstName = params.firstName
employee.lastName = params.lastName
// update the database
employee.save()
}
}
Grails 可以透過允許類似這樣的方式來簡化
class EmployeeController {
def updateEmployee() {
// retrieve the employee from the database
def employee = Employee.get(params.id)
// update properties in the employee
employee.properties = params
// update the database
employee.save()
}
}
這些示例都假設存在名為 firstName 和 lastName 的請求引數。在第一個示例中,我們需要更新的每個屬性都有一行程式碼,而在第二個示例中,我們只有 1 行程式碼處理所有需要更新的屬性。
在這個特定示例中,我們只減少了 1 行程式碼,但如果 Employee 物件中有許多屬性需要更新,第一個示例會變得更長更繁瑣,而第二個示例則完全不變。
程式碼清單 3 比程式碼清單 2 更簡潔,需要的維護更少,但這對於任何特定用例來說可能不是最好的做法。
這種更簡單方法的一個問題是它可能允許使用者更新應用程式開發者不打算允許的屬性。
例如,如果存在名為 salary 的請求引數,程式碼清單 2 中的程式碼會忽略該請求引數,但程式碼清單 3 中的程式碼會使用該引數的值來更新 Employee 物件中的 salary 屬性,這可能會有問題。
應用程式程式碼可以使用幾種技術來防禦類似問題。一種是使用程式碼清單 2 中所示的方法。另一種是在要求進行資料繫結時,向 Grails 提供屬性名稱的白名單或黑名單。
這裡展示了一種提供白名單的方法
class EmployeeController {
def updateEmployee() {
// retrieve the employee from the database
def employee = Employee.get(params.id)
// update the firstName and lastName properties in the employee
employee.properties['firstName', 'lastName'] = params
// update the database
employee.save()
}
}
程式碼清單 4 中的程式碼只會將 firstName 和 lastName 請求引數繫結到 employee 物件,忽略所有其他請求引數。如果存在名為 salary 的請求引數,它不會導致 employee 物件中的 salary 屬性被更新。
另一種技術是使用新增到所有 Grails 控制器中的 bindData 方法。bindData 方法允許提供屬性名稱的白名單和/或黑名單
class EmployeeController {
def updateEmployee() {
// retrieve the employee from the database
def employee = Employee.get(params.id)
// update the firstName and lastName properties in the employee
bindData(employee, params, [include: ['firstName', 'lastName']])
// or... bindData(employee, params, [exclude: ['salary']])
// update the database
employee.save()
}
}
考慮像這樣的程式碼
class TaxCalculator {
def taxRate
def calculateTax(baseAmount) {
baseAmount * taxRate
}
}
class InvoiceHelper {
def taxCalculator
def calculateInvoice(...) {
// do something with the parameters that involves invoking
// taxCalculator.calculateTax(...) to generate some total
}
}
考慮在 Spring 應用程式上下文中配置了一個 TaxCalculator 例項以及一個 InvoiceHelper 例項。TaxCalculator 例項被自動注入到 InvoiceHelper 例項中。
現在考慮一個像這樣的 Grails 域類
class Vendor {
def invoiceHelper
String vendorName
// ...
}
一個 Grails 控制器可能會執行類似這樣的操作來更新當前持久化在資料庫中的 Vendor
class VendorController {
def updateVendor = {
// retrieve the vendor from the database
def vendor = Vendor.get(params.id)
// update properties in the vendor
vendor.properties = params
// update the database
vendor.save()
}
}
這潛在的問題在於,它可能會無意中允許更新 Spring 應用程式上下文中的 TaxCalculator 例項中的 taxRate 屬性。
如果存在一個名為 invoiceHelper.taxCalculator.taxRate 的請求引數,當執行 "vendor.properties = params" 時,就會發生這種情況。根據應用程式中的其他一些細節,這可能會導致應用程式出現意外且有問題的行為。
在 Grails 2.0.2 中,這不會是問題,因為 Vendor 類中的 invoiceHelper 屬性是動態型別的,並且如下文所述,動態型別的屬性除非明確包含在白名單中,否則不可繫結。如果 invoiceHelper 屬性是靜態型別的,那麼它將受到資料繫結的影響。
在 Grails 2.0.2 之前,程式碼清單 8 中的程式碼是有問題的,但可以使用上面描述的白名單或黑名單技術輕鬆解決。
使用資料繫結建構函式時會出現同一個問題的另一種情況
class VendorController {
def createVendor = {
// create a new Vendor
def vendor = new Vendor(params)
// save to the database
vendor.save()
}
}
在 Grails 2.0.2 和 Grails 1.3.8 之前,執行 "new Vendor(params)" 時會發生以下情況:建立 Vendor 物件,然後對 Vendor 例項執行依賴注入,然後將 params 繫結到該例項上執行資料繫結。
由於事件的順序,如果 params 包含一個名為 "invoiceHelper.taxCalculator.taxRate" 的請求引數,那麼這段程式碼就會受到上面描述的同樣問題的影響。
在 Grails 2.0.2 和 Grails 1.3.8 中,事件順序發生了變化,因此先建立 Vendor 物件,然後對例項執行資料繫結,最後執行依賴注入。
透過這種事件順序,資料繫結不會有更改 Spring bean 屬性的風險,因為 Spring bean 是在資料繫結發生後才注入的。
對於 Grails 2.0.2 和 Grails 1.3.8 之前的版本,管理這個問題的簡單方法是這樣的
class VendorController {
def createVendor = {
// create a new Vendor
def vendor = new Vendor()
vendor.properties['vendorName'] = params
// or... bindData(vendor, params, [include: ['vendorName']])
// or... bindData(vendor, params, [exclude: ['invoiceHelper']])
// save to the database
vendor.save()
}
}
這並非對所有域類都有問題,但對於那些自動注入了 Spring bean 的域類來說則潛在有問題。順便說一句,同樣的問題也適用於 Grails 命令物件,它們也受到資料繫結和自動依賴注入的影響。
這些技術都得到了 Grails 長期以來的支援。Grails 2.0.2 將在資料繫結管理方面提供更多靈活性。在 Grails 2.0.2 中,程式碼清單 4 和 5 中的程式碼行為與之前版本完全相同。提供了白名單或黑名單時,它們將受到尊重。
然而,當未提供白名單或黑名單時,如在 "employee.properties = params" 中,Grails 2.0.2 的行為可能有所不同,具體取決於 Employee 類中的一些細節。
在 Grails 2.0.2 中,資料繫結機制預設將排除所有靜態、瞬時或動態型別的屬性。為了更精細地控制哪些屬性預設可繫結而哪些不可繫結,Grails 2.0.2 支援一個新的 bindable 約束
class Employee {
String firstName
String lastName
BigDecimal salary
static constraints = {
salary bindable: false
}
}
程式碼清單 11 展示瞭如何表示 salary 屬性預設不可繫結。這意味著當應用程式執行諸如 "employee.properties = params" 的操作時,salary 屬性將不會受到資料繫結的影響。
如果該屬性明確包含在白名單中,例如 "employee.properties['firstName', 'lastName', 'salary'] = params",那麼它將受到資料繫結的影響。