mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
add options
This commit is contained in:
parent
c85f204da9
commit
95854cddb1
@ -26,6 +26,7 @@
|
|||||||
* 【core 】 增加CharSequenceUtil.subPreGbk重载(issue#I4JO2E@Gitee)
|
* 【core 】 增加CharSequenceUtil.subPreGbk重载(issue#I4JO2E@Gitee)
|
||||||
* 【core 】 ReflectUtil.getMethod排除桥接方法(pr#1965@Github)
|
* 【core 】 ReflectUtil.getMethod排除桥接方法(pr#1965@Github)
|
||||||
* 【http 】 completeFileNameFromHeader在使用path为路径时,自动解码(issue#I4K0FS@Gitee)
|
* 【http 】 completeFileNameFromHeader在使用path为路径时,自动解码(issue#I4K0FS@Gitee)
|
||||||
|
* 【core 】 CopyOptions增加override配置(issue#I4JQ1N@Gitee)
|
||||||
*
|
*
|
||||||
### 🐞Bug修复
|
### 🐞Bug修复
|
||||||
* 【core 】 修复FileResource构造fileName参数无效问题(issue#1942@Github)
|
* 【core 】 修复FileResource构造fileName参数无效问题(issue#1942@Github)
|
||||||
|
@ -121,18 +121,19 @@ public class PropDesc {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查属性是否可读(即是否可以通过{@link #getValue(Object)}获取到值)
|
* 检查属性是否可读(即是否可以通过{@link #getValue(Object)}获取到值)
|
||||||
|
*
|
||||||
* @param checkTransient 是否检查Transient关键字或注解
|
* @param checkTransient 是否检查Transient关键字或注解
|
||||||
* @return 是否可读
|
* @return 是否可读
|
||||||
* @since 5.4.2
|
* @since 5.4.2
|
||||||
*/
|
*/
|
||||||
public boolean isReadable(boolean checkTransient){
|
public boolean isReadable(boolean checkTransient) {
|
||||||
// 检查是否有getter方法或是否为public修饰
|
// 检查是否有getter方法或是否为public修饰
|
||||||
if(null == this.getter && false == ModifierUtil.isPublic(this.field)){
|
if (null == this.getter && false == ModifierUtil.isPublic(this.field)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查transient关键字和@Transient注解
|
// 检查transient关键字和@Transient注解
|
||||||
if(checkTransient && isTransientForGet()){
|
if (checkTransient && isTransientForGet()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +165,7 @@ public class PropDesc {
|
|||||||
* 首先调用字段对应的Getter方法获取值,如果Getter方法不存在,则判断字段如果为public,则直接获取字段值
|
* 首先调用字段对应的Getter方法获取值,如果Getter方法不存在,则判断字段如果为public,则直接获取字段值
|
||||||
*
|
*
|
||||||
* @param bean Bean对象
|
* @param bean Bean对象
|
||||||
* @param targetType 返回属性值需要转换的类型,null表示不转换
|
* @param targetType 返回属性值需要转换的类型,null表示不转换
|
||||||
* @param ignoreError 是否忽略错误,包括转换错误和注入错误
|
* @param ignoreError 是否忽略错误,包括转换错误和注入错误
|
||||||
* @return this
|
* @return this
|
||||||
* @since 5.4.2
|
* @since 5.4.2
|
||||||
@ -190,18 +191,19 @@ public class PropDesc {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查属性是否可读(即是否可以通过{@link #getValue(Object)}获取到值)
|
* 检查属性是否可读(即是否可以通过{@link #getValue(Object)}获取到值)
|
||||||
|
*
|
||||||
* @param checkTransient 是否检查Transient关键字或注解
|
* @param checkTransient 是否检查Transient关键字或注解
|
||||||
* @return 是否可读
|
* @return 是否可读
|
||||||
* @since 5.4.2
|
* @since 5.4.2
|
||||||
*/
|
*/
|
||||||
public boolean isWritable(boolean checkTransient){
|
public boolean isWritable(boolean checkTransient) {
|
||||||
// 检查是否有getter方法或是否为public修饰
|
// 检查是否有getter方法或是否为public修饰
|
||||||
if(null == this.setter && false == ModifierUtil.isPublic(this.field)){
|
if (null == this.setter && false == ModifierUtil.isPublic(this.field)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查transient关键字和@Transient注解
|
// 检查transient关键字和@Transient注解
|
||||||
if(checkTransient && isTransientForSet()){
|
if (checkTransient && isTransientForSet()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,7 +241,28 @@ public class PropDesc {
|
|||||||
* @since 5.4.2
|
* @since 5.4.2
|
||||||
*/
|
*/
|
||||||
public PropDesc setValue(Object bean, Object value, boolean ignoreNull, boolean ignoreError) {
|
public PropDesc setValue(Object bean, Object value, boolean ignoreNull, boolean ignoreError) {
|
||||||
if (ignoreNull && null == value) {
|
return setValue(bean, value, ignoreNull, ignoreError, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置属性值,可以自动转换字段类型为目标类型
|
||||||
|
*
|
||||||
|
* @param bean Bean对象
|
||||||
|
* @param value 属性值,可以为任意类型
|
||||||
|
* @param ignoreNull 是否忽略{@code null}值,true表示忽略
|
||||||
|
* @param ignoreError 是否忽略错误,包括转换错误和注入错误
|
||||||
|
* @param override 是否覆盖目标值,如果不覆盖,会先读取bean的值,非{@code null}则写,否则忽略。如果覆盖,则不判断直接写
|
||||||
|
* @return this
|
||||||
|
* @since 5.7.17
|
||||||
|
*/
|
||||||
|
public PropDesc setValue(Object bean, Object value, boolean ignoreNull, boolean ignoreError, boolean override) {
|
||||||
|
if (null == value && ignoreNull) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// issue#I4JQ1N@Gitee
|
||||||
|
// 非覆盖模式下,如果目标值存在,则跳过
|
||||||
|
if (false == override && null != getValue(bean)) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,13 +140,44 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
|
|||||||
* Map转Map
|
* Map转Map
|
||||||
*
|
*
|
||||||
* @param source 源Map
|
* @param source 源Map
|
||||||
* @param dest 目标Map
|
* @param targetMap 目标Map
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
private void mapToMap(Map source, Map dest) {
|
private void mapToMap(Map source, Map targetMap) {
|
||||||
if (null != dest && null != source) {
|
source.forEach((key, value)->{
|
||||||
dest.putAll(source);
|
final CopyOptions copyOptions = this.copyOptions;
|
||||||
}
|
final HashSet<String> ignoreSet = (null != copyOptions.ignoreProperties) ? CollUtil.newHashSet(copyOptions.ignoreProperties) : null;
|
||||||
|
|
||||||
|
// issue#I4JQ1N@Gitee
|
||||||
|
// 非覆盖模式下,如果目标值存在,则跳过
|
||||||
|
if(false == copyOptions.override && null != targetMap.get(key)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(key instanceof CharSequence){
|
||||||
|
if (CollUtil.contains(ignoreSet, key)) {
|
||||||
|
// 目标属性值被忽略或值提供者无此key时跳过
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对key做映射,映射后为null的忽略之
|
||||||
|
key = copyOptions.editFieldName(copyOptions.getMappedFieldName(key.toString(), false));
|
||||||
|
if(null == key){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = copyOptions.editFieldValue(key.toString(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ((null == value && copyOptions.ignoreNullValue) || source == value) {
|
||||||
|
// 当允许跳过空时,跳过
|
||||||
|
//值不能为bean本身,防止循环引用,此类也跳过
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetMap.put(key, value);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -158,11 +189,11 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
private void beanToMap(Object bean, Map targetMap) {
|
private void beanToMap(Object bean, Map targetMap) {
|
||||||
final HashSet<String> ignoreSet = (null != copyOptions.ignoreProperties) ? CollUtil.newHashSet(copyOptions.ignoreProperties) : null;
|
|
||||||
final CopyOptions copyOptions = this.copyOptions;
|
final CopyOptions copyOptions = this.copyOptions;
|
||||||
|
final HashSet<String> ignoreSet = (null != copyOptions.ignoreProperties) ? CollUtil.newHashSet(copyOptions.ignoreProperties) : null;
|
||||||
|
|
||||||
BeanUtil.descForEach(bean.getClass(), (prop)->{
|
BeanUtil.descForEach(bean.getClass(), (prop)->{
|
||||||
if(false == prop.isReadable(copyOptions.isTransientSupport())){
|
if(false == prop.isReadable(copyOptions.transientSupport)){
|
||||||
// 忽略的属性跳过之
|
// 忽略的属性跳过之
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -178,6 +209,12 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// issue#I4JQ1N@Gitee
|
||||||
|
// 非覆盖模式下,如果目标值存在,则跳过
|
||||||
|
if(false == copyOptions.override && null != targetMap.get(key)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Object value;
|
Object value;
|
||||||
try {
|
try {
|
||||||
value = prop.getValue(bean);
|
value = prop.getValue(bean);
|
||||||
@ -230,7 +267,7 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
|
|||||||
|
|
||||||
// 遍历目标bean的所有属性
|
// 遍历目标bean的所有属性
|
||||||
BeanUtil.descForEach(actualEditable, (prop)->{
|
BeanUtil.descForEach(actualEditable, (prop)->{
|
||||||
if(false == prop.isWritable(this.copyOptions.isTransientSupport())){
|
if(false == prop.isWritable(this.copyOptions.transientSupport)){
|
||||||
// 字段不可写,跳过之
|
// 字段不可写,跳过之
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -270,7 +307,7 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
prop.setValue(bean, value, copyOptions.ignoreNullValue, copyOptions.ignoreError);
|
prop.setValue(bean, value, copyOptions.ignoreNullValue, copyOptions.ignoreError, copyOptions.override);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,11 @@ public class CopyOptions implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* 是否支持transient关键字修饰和@Transient注解,如果支持,被修饰的字段或方法对应的字段将被忽略。
|
* 是否支持transient关键字修饰和@Transient注解,如果支持,被修饰的字段或方法对应的字段将被忽略。
|
||||||
*/
|
*/
|
||||||
private boolean transientSupport = true;
|
protected boolean transientSupport = true;
|
||||||
|
/**
|
||||||
|
* 是否覆盖目标值,如果不覆盖,会先读取目标对象的值,非{@code null}则写,否则忽略。如果覆盖,则不判断直接写
|
||||||
|
*/
|
||||||
|
protected boolean override = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建拷贝选项
|
* 创建拷贝选项
|
||||||
@ -259,7 +263,9 @@ public class CopyOptions implements Serializable {
|
|||||||
*
|
*
|
||||||
* @return 是否支持
|
* @return 是否支持
|
||||||
* @since 5.4.2
|
* @since 5.4.2
|
||||||
|
* @deprecated 无需此方法,内部使用直接调用属性
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public boolean isTransientSupport() {
|
public boolean isTransientSupport() {
|
||||||
return this.transientSupport;
|
return this.transientSupport;
|
||||||
}
|
}
|
||||||
@ -276,6 +282,18 @@ public class CopyOptions implements Serializable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置是否覆盖目标值,如果不覆盖,会先读取目标对象的值,非{@code null}则写,否则忽略。如果覆盖,则不判断直接写
|
||||||
|
*
|
||||||
|
* @param override 是否覆盖目标值
|
||||||
|
* @return this
|
||||||
|
* @since 5.7.17
|
||||||
|
*/
|
||||||
|
public CopyOptions setOverride(boolean override) {
|
||||||
|
this.override = override;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得映射后的字段名<br>
|
* 获得映射后的字段名<br>
|
||||||
* 当非反向,则根据源字段名获取目标字段名,反之根据目标字段名获取源字段名。
|
* 当非反向,则根据源字段名获取目标字段名,反之根据目标字段名获取源字段名。
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
package cn.hutool.core.bean.copier;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class BeanCopierTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试在非覆盖模式下,目标对象有值则不覆盖
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void beanToBeanNotOverrideTest() {
|
||||||
|
final A a = new A();
|
||||||
|
a.setValue("123");
|
||||||
|
final B b = new B();
|
||||||
|
b.setValue("abc");
|
||||||
|
|
||||||
|
final BeanCopier<B> copier = BeanCopier.create(a, b, CopyOptions.create().setOverride(false));
|
||||||
|
copier.copy();
|
||||||
|
|
||||||
|
Assert.assertEquals("abc", b.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试在覆盖模式下,目标对象值被覆盖
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void beanToBeanOverrideTest() {
|
||||||
|
final A a = new A();
|
||||||
|
a.setValue("123");
|
||||||
|
final B b = new B();
|
||||||
|
b.setValue("abc");
|
||||||
|
|
||||||
|
final BeanCopier<B> copier = BeanCopier.create(a, b, CopyOptions.create());
|
||||||
|
copier.copy();
|
||||||
|
|
||||||
|
Assert.assertEquals("123", b.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class A {
|
||||||
|
private String value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class B {
|
||||||
|
private String value;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user