領先一步
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
}
}
假設 TaxCalculator 的實例與 InvoiceHelper 的實例一起在 Spring 應用程式內容中配置。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 實例執行依賴注入,然後執行資料繫結,將參數繫結到實例。
由於事件的順序,如果參數包含名為 "invoiceHelper.taxCalculator.taxRate" 的請求參數,則此程式碼會遇到上述相同問題。
在 Grails 2.0.2 和 Grails 1.3.8 中,事件的順序已變更,因此建立 Vendor 物件,然後對實例執行資料繫結,然後執行依賴注入。
透過該事件序列,不會有資料繫結變更 Spring Bean 中的屬性的危險,因為 Spring Bean 要在資料繫結發生後才會注入。
對於 Grails 2.0.2 和 Grails 1.3.8 之前的 Grails 版本,管理此問題的簡單方法如下
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",則它將受到資料繫結的約束。