領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多其中包括許多簡化與資料繫結相關的複雜而繁瑣的問題的事物。總的來說,Grails 使資料繫結變得非常簡單,因為它提供了幾種將資料對映繫結到物件圖的技術。
應用程式開發人員理解每種技術的含義非常重要,以便決定哪種技術對於任何給定的用例最合適且最安全。
考慮一個看起來像這樣的領域類
class Employee {
String firstName
String lastName
BigDecimal salary
}
應用程式中可能有一個表單,允許更新 firstName 和 lastName 屬性。該表單可能不允許更新 salary 屬性,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 行程式碼,但如果 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 包含一個名為“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”,那麼它將受資料繫結。