领先一步
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”,那么它将受数据绑定。