Скопируйте свойства класса Groovy
Я хочу скопировать свойства объекта в другой объект общим способом (если свойство существует для целевого объекта, я копирую его из исходного объекта).
Мой код работает нормально, используя 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
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] }
}
По сути, он сначала вычисляет общие свойства между двумя объектами, а затем копирует их. Это также работает, но я думаю, что первый более прост и понятен:)
Иногда меньше значит больше.
Я думаю, что лучший и понятный способ - это использовать метод 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)
}
Еще один способ сделать:
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 ] ] } )
}
}
Spring BeanUtils.copyProperties будет работать, даже если исходные / целевые классы имеют разные типы. http://docs.spring.io/autorepo/docs/spring/3.2.3.RELEASE/javadoc-api/org/springframework/beans/BeanUtils.html