【问题标题】:Copy Groovy class properties复制 Groovy 类属性
【发布时间】:2012-02-22 17:52:54
【问题描述】:

我想以通用方式将对象属性复制到另一个对象(如果目标对象上存在属性,我会从源对象中复制它)。

我的代码使用ExpandoMetaClass 可以正常工作,但我不喜欢这个解决方案。还有其他方法可以做到这一点吗?

class User {
    String name = 'Arturo'
    String city = 'Madrid'
    Integer age = 27
}

class AdminUser {
    String name
    String city
    Integer age
}

def copyProperties(source, target) {
    target.properties.each { key, value ->
        if (source.metaClass.hasProperty(source, key) && key != 'class' && key != 'metaClass') {
            target.setProperty(key, source.metaClass.getProperty(source, key))
        }
    }
}

def (user, adminUser) = [new User(), new AdminUser()]
assert adminUser.name == null
assert adminUser.city == null
assert adminUser.age == null

copyProperties(user, adminUser)
assert adminUser.name == 'Arturo'
assert adminUser.city == 'Madrid'
assert adminUser.age == 27

【问题讨论】:

标签: class groovy properties property-list expandometaclass


【解决方案1】:

即使源/目标类是不同的类型,Spring BeanUtils.copyProperties 也可以工作。 http://docs.spring.io/autorepo/docs/spring/3.2.3.RELEASE/javadoc-api/org/springframework/beans/BeanUtils.html

【讨论】:

【解决方案2】:

我认为最好的清晰的方法是使用InvokerHelper.setProperties方法

例子:

import groovy.transform.ToString
import org.codehaus.groovy.runtime.InvokerHelper

@ToString
class User {
    String name = 'Arturo'
    String city = 'Madrid'
    Integer age = 27
}

@ToString
class AdminUser {
    String name
    String city
    Integer age
}

def user = new User()
def adminUser = new AdminUser()

println "before: $user $adminUser"
InvokerHelper.setProperties(adminUser, user.properties)
println "after : $user $adminUser"

输出:

before: User(Arturo, Madrid, 27) AdminUser(null, null, null)
after : User(Arturo, Madrid, 27) AdminUser(Arturo, Madrid, 27)

注意:如果你想要更多的可读性,你可以使用分类

use(InvokerHelper) {
    adminUser.setProperties(user.properties) 
}

【讨论】:

  • 感谢 InvokerHelper,为这个问题苦苦挣扎了一段时间。
  • 我认为这是一个好主意,它在 DomainUnitTest 案例中工作,然后在没有太多警告的情况下,它在服务集成测试中失败了! grrr....为什么!
  • 好的,结果是我把它们弄反了,我的测试只是测试了两者是否相等。该副本已将它们都变为空!
【解决方案3】:

另一种方法是:

def copyProperties( source, target ) {
  [source,target]*.getClass().declaredFields*.grep { !it.synthetic }.name.with { a, b ->
    a.intersect( b ).each {
      target."$it" = source."$it"
    }
  }
}

获取通用属性(不是合成字段),然后将它们分配给目标


您还可以(使用此方法)执行以下操作:

def user = new User()

def propCopy( src, clazz ) {
  [src.getClass(), clazz].declaredFields*.grep { !it.synthetic }.name.with { a, b ->
    clazz.newInstance().with { tgt ->
      a.intersect( b ).each {
        tgt[ it ] = src[ it ]
      }
      tgt
    }
  }
}


def admin = propCopy( user, AdminUser )
assert admin.name == 'Arturo'
assert admin.city == 'Madrid'
assert admin.age == 27

因此,您将一个对象传递给该方法以从中复制属性,以及返回对象的类。然后该方法创建这个类的一个新实例(假设是一个无参数的构造函数),设置属性并返回它。


编辑 2

假设这些是 Groovy 类,您可以调用 Map 构造函数并像这样设置所有公共属性:

def propCopy( src, clazz ) {
  [src.getClass(), clazz].declaredFields*.grep { !it.synthetic }.name.with { a, b ->
    clazz.metaClass.invokeConstructor( a.intersect( b ).collectEntries { [ (it):src[ it ] ] } )
  }
}

【讨论】:

【解决方案4】:

我认为您的解决方案非常好,并且在正确的轨道上。至少我觉得可以理解。

该解决方案的更简洁版本可能是...

def copyProperties(source, target) {
    source.properties.each { key, value ->
        if (target.hasProperty(key) && !(key in ['class', 'metaClass'])) 
            target[key] = value
    }
}

...但本质上并没有什么不同。我正在迭代源属性,因此我可以使用这些值分配给目标:)。不过,它可能不如您的原始解决方案强大,因为我认为如果目标对象定义了 getAt(String) 方法,它会中断。

如果你想变得花哨,你可以这样做:

def copyProperties(source, target) {
    def (sProps, tProps) = [source, target]*.properties*.keySet()
    def commonProps = sProps.intersect(tProps) - ['class', 'metaClass']
    commonProps.each { target[it] = source[it] }
}

基本上,它首先计算两个对象之间的共同属性,然后复制它们。它也有效,但我认为第一个更简单,更容易理解:)

有时少即是多。

【讨论】:

  • 对于单线爱好者,我认为这样的事情应该可以工作:[source, target]*.properties*.keySet().grep { it != 'class' && it != 'metaClass' }.each { target[it] = source[it] }
  • 可能还想将目标赋值放在 try catch 中,以防万一无法强制类型
  • 调试时在 Idea 中工作,但在部署的 jar 上调用时抛出异常:“方法没有签名:java.util.ArrayList.keySet() 适用于参数类型:() 值: []\n可能的解决方案:toSet(), toSet(), set(int, java.lang.Object), set(int, java.lang.Object), get(int), get(int)
  • 当参数有一个名为 properties 的属性或一个名为 getProperties() 且参数为零的方法时,此操作将失败。这些案例的答案可以在这里找到:stackoverflow.com/a/46979194/212749
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-04
  • 2012-02-10
  • 1970-01-01
  • 2022-01-12
  • 2015-02-27
  • 1970-01-01
相关资源
最近更新 更多