com.feilong.core.bean.PropertyUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of feilong Show documentation
Show all versions of feilong Show documentation
feilong is a suite of core and expanded libraries that include utility classes, http, excel,cvs, io classes, and much much more.
/*
* Copyright (C) 2008 feilong
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.feilong.core.bean;
import static com.feilong.core.Validator.isNotNullOrEmpty;
import static com.feilong.core.Validator.isNullOrEmpty;
import static com.feilong.core.util.MapUtil.newLinkedHashMap;
import java.beans.PropertyDescriptor;
import java.util.Collection;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.feilong.core.Validate;
import com.feilong.core.lang.ClassUtil;
import com.feilong.lib.beanutils.PropertyUtils;
import com.feilong.lib.lang3.ClassUtils;
import com.feilong.tools.slf4j.Slf4jUtil;
/**
* 对 {@link com.feilong.lib.beanutils.PropertyUtils}的再次封装.
*
* 说明:
*
*
* - 目的是将原来的 checkedException 异常 转换成 {@link BeanOperationException}
*
*
*
* {@link PropertyUtils}与 {@link com.feilong.lib.beanutils.BeanUtils}:
*
*
*
* {@link PropertyUtils}类和{@link com.feilong.lib.beanutils.BeanUtils}类很多的方法在参数上都是相同的,但返回值不同.
* {@link com.feilong.lib.beanutils.BeanUtils}着重于"Bean",返回值通常是{@link String},
* 而{@link PropertyUtils}着重于属性,它的返回值通常是{@link Object}.
*
*
*
* @author feilong
* @see com.feilong.lib.beanutils.PropertyUtils
* @see com.feilong.core.bean.BeanUtil
* @since 1.0.0
*/
public final class PropertyUtil{
/** The Constant LOGGER. */
private static final Logger LOGGER = LoggerFactory.getLogger(PropertyUtil.class);
/**
* 传入的bean 是null 的消息提示.
*
* @since 2.1.0
*/
private static final String MESSAGE_BEAN_IS_NULL = "bean can't be null!";
/**
* 传入的propertyName 是blank 的消息提示.
*
* @since 2.1.0
*/
private static final String MESSAGE_PROPERTYNAME_IS_BLANK = "propertyName can't be blank!";
//---------------------------------------------------------------
/** Don't let anyone instantiate this class. */
private PropertyUtil(){
//AssertionError不是必须的. 但它可以避免不小心在类的内部调用构造器. 保证该类在任何情况下都不会被实例化.
//see 《Effective Java》 2nd
throw new AssertionError("No " + getClass().getName() + " instances for you!");
}
//---------------------------------------------------------------
/**
* Retrieve the property descriptors for the specified class, introspecting and caching them the first time a particular bean class is
* encountered.
*
* @param klass
* the klass
* @return 如果 klass
是null,抛出 {@link NullPointerException}
* @since 3.0.0
*/
public static PropertyDescriptor[] getPropertyDescriptors(Class klass){
Validate.notNull(klass, "klass can't be null!");
try{
return PropertyUtils.getPropertyDescriptors(klass);
}catch (Exception e){
String pattern = "getPropertyDescriptors exception,klass:[{}]";
throw new BeanOperationException(Slf4jUtil.format(pattern, klass), e);
}
}
//---------------------------------------------------------------
/**
* 将 fromObj
中的全部或者一组属性的值,复制到 toObj
对象中.
*
* 注意点:
*
*
*
*
* - 如果
toObj
是null,抛出 {@link NullPointerException}
* - 如果
fromObj
是null,抛出 {@link NullPointerException}
* - 对于Date类型,不需要先注册converter
* - 这种copy都是 浅拷贝,复制后的2个Bean的同一个属性可能拥有同一个对象的ref,这个在使用时要小心,特别是对于属性为自定义类的情况 .
*
*
*
* 使用示例:
*
*
*
*
* User oldUser = new User();
* oldUser.setId(5L);
* oldUser.setMoney(new BigDecimal(500000));
* oldUser.setDate(now());
* oldUser.setNickName(ConvertUtil.toArray("feilong", "飞天奔月", "venusdrogon"));
*
* User newUser = new User();
* PropertyUtil.copyProperties(newUser, oldUser, "date", "money", "nickName");
* LOGGER.debug(JsonUtil.format(newUser));
*
*
* 返回:
*
*
* {
* "date": "2015-09-06 13:27:43",
* "id": 0,
* "nickName": [
* "feilong",
* "飞天奔月",
* "venusdrogon"
* ],
* "age": 0,
* "name": "feilong",
* "money": 500000,
* "userInfo": {"age": 0}
* }
*
*
*
*
* 重构:
*
*
*
*
* 对于以下代码:
*
*
*
*
* private ContactCommand toContactCommand(ShippingInfoSubForm shippingInfoSubForm){
* ContactCommand contactCommand = new ContactCommand();
* contactCommand.setCountryId(shippingInfoSubForm.getCountryId());
* contactCommand.setProvinceId(shippingInfoSubForm.getProvinceId());
* contactCommand.setCityId(shippingInfoSubForm.getCityId());
* contactCommand.setAreaId(shippingInfoSubForm.getAreaId());
* contactCommand.setTownId(shippingInfoSubForm.getTownId());
* return contactCommand;
* }
*
*
*
* 可以重构成:
*
*
*
* private ContactCommand toContactCommand(ShippingInfoSubForm shippingInfoSubForm){
* ContactCommand contactCommand = new ContactCommand();
* PropertyUtil.copyProperties(contactCommand, shippingInfoSubForm, "countryId", "provinceId", "cityId", "areaId", "townId");
* return contactCommand;
* }
*
*
*
* 可以看出,代码更精简,目的性更明确
*
*
*
*
* {@link com.feilong.lib.beanutils.BeanUtils#copyProperties(Object, Object)}与
* {@link PropertyUtils#copyProperties(Object, Object)}区别
*
*
*
* - {@link com.feilong.lib.beanutils.BeanUtils#copyProperties(Object, Object) BeanUtils}
* 提供类型转换功能,即发现两个JavaBean的同名属性为不同类型时,在支持的数据类型范围内进行转换,
* 而 {@link PropertyUtils#copyProperties(Object, Object) PropertyUtils}不支持这个功能,但是速度会更快一些.
* - commons-beanutils v1.9.0以前的版本 BeanUtils不允许对象的属性值为 null,PropertyUtils可以拷贝属性值 null的对象.
* (注:commons-beanutils v1.9.0+修复了这个情况,BeanUtilsBean.copyProperties() no longer throws a ConversionException for null properties
* of certain data types),具体参阅commons-beanutils的
* RELEASE-NOTES.txt
*
*
*
* 相比较直接调用 {@link PropertyUtils#copyProperties(Object, Object)}的优点:
*
*
* - 将 checkedException 异常转成了 {@link BeanOperationException} RuntimeException,因为通常copy的时候出现了checkedException,也是普普通通记录下log,没有更好的处理方式
*
* - 支持 includePropertyNames 参数,允许针对性copy 个别属性
* - 更多,更容易理解的的javadoc
*
*
*
* @param toObj
* 目标对象
* @param fromObj
* 原始对象
* @param includePropertyNames
* 包含的属性数组名字数组,(can be nested/indexed/mapped/combo)
* 如果是null或者empty,将会调用 {@link PropertyUtils#copyProperties(Object, Object)}
*
* - 如果没有传入
includePropertyNames
参数,那么直接调用{@link PropertyUtils#copyProperties(Object, Object)},否则循环调用
* {@link #getProperty(Object, String)}再{@link #setProperty(Object, String, Object)}到toObj
对象中
* - 如果传入的
includePropertyNames
,含有 fromObj
没有的属性名字,将会抛出异常
* - 如果传入的
includePropertyNames
,含有 fromObj
有,但是 toObj
没有的属性名字,会抛出异常,see
* {@link com.feilong.lib.beanutils.PropertyUtilsBean#setSimpleProperty(Object, String, Object) copyProperties}
* Line2078
*
* @throws NullPointerException
* 如果 toObj
是null,或者 fromObj
是null
* @throws BeanOperationException
* 如果在copy的过程中,有任何的checkedException,将会被转成该异常返回
* @see #setProperty(Object, String, Object)
* @see BeanUtil#copyProperties(Object, Object, String...)
* @see "org.apache.commons.beanutils.PropertyUtilsBean#copyProperties(Object, Object)"
* @see Bean复制的几种框架性能比较(Apache BeanUtils、PropertyUtils,Spring
* BeanUtils,Cglib BeanCopier)
* @since 1.4.1
*/
public static void copyProperties(Object toObj,Object fromObj,String...includePropertyNames){
Validate.notNull(toObj, "toObj [destination bean] not specified!");
Validate.notNull(fromObj, "fromObj [origin bean] not specified!");
//---------------------------------------------------------------
if (isNullOrEmpty(includePropertyNames)){
try{
PropertyUtils.copyProperties(toObj, fromObj);
return;
}catch (Exception e){
String pattern = "copyProperties exception,toObj:[{}],fromObj:[{}],includePropertyNames:[{}]";
throw new BeanOperationException(Slf4jUtil.format(pattern, toObj, fromObj, includePropertyNames), e);
}
}
//---------------------------------------------------------------
for (String propertyName : includePropertyNames){
Object value = getProperty(fromObj, propertyName);
setProperty(toObj, propertyName, value);
}
}
//---------------------------------------------------------------
/**
* 返回一个 bean
中指定属性 propertyNames
可读属性,并将属性名/属性值放入一个
* {@link java.util.LinkedHashMap LinkedHashMap} 中.
*
* 示例:
*
*
*
*
* 场景: 取到user bean里面所有的属性成map
*
*
*
* User user = new User();
* user.setId(5L);
* user.setDate(now());
*
* LOGGER.debug(JsonUtil.format(PropertyUtil.describe(user));
*
*
* 返回:
*
*
* {
* "id": 5,
* "name": "feilong",
* "age": null,
* "date": "2016-07-13 22:18:26"
* }
*
*
*
*
*
* 场景: 提取user bean "date"和 "id"属性:
*
*
*
* User user = new User();
* user.setId(5L);
* user.setDate(now());
*
* LOGGER.debug(JsonUtil.format(PropertyUtil.describe(user, "date", "id"));
*
*
* 返回的结果,按照指定参数名称顺序:
*
*
* {
* "date": "2016-07-13 22:21:24",
* "id": 5
* }
*
*
*
*
* 说明:
*
*
* - 另外还有一个名为class的属性,属性值是Object的类名,事实上class是java.lang.Object的一个属性
* - 如果
propertyNames
是null或者 empty,那么获取所有属性的值
* - map的key按照
propertyNames
的顺序
*
*
*
* 原理:
*
*
*
* - 取到bean class的 {@link java.beans.PropertyDescriptor}数组
* - 循环,找到 {@link java.beans.PropertyDescriptor#getReadMethod()}
* - 将 name and {@link com.feilong.lib.beanutils.PropertyUtilsBean#getProperty(Object, String)} 设置到map中
*
*
*
* @param bean
* Bean whose properties are to be extracted
* @param propertyNames
* 属性名称 (can be nested/indexed/mapped/combo),参见 propertyName
* @return 如果 propertyNames
是null或者empty,返回 {@link PropertyUtils#describe(Object)}
* @throws NullPointerException
* 如果 bean
是null,或者propertyNames
包含 null的元素
* @throws IllegalArgumentException
* 如果 propertyNames
包含 blank的元素
* @see com.feilong.lib.beanutils.BeanUtils#describe(Object)
* @see com.feilong.lib.beanutils.PropertyUtils#describe(Object)
* @since 1.8.0
*/
public static Map describe(Object bean,String...propertyNames){
Validate.notNull(bean, MESSAGE_BEAN_IS_NULL);
//---------------------------------------------------------------
if (isNullOrEmpty(propertyNames)){
try{
return PropertyUtils.describe(bean);
}catch (Exception e){
String pattern = "describe exception,bean:[{}],propertyNames:[{}]";
throw new BeanOperationException(Slf4jUtil.format(pattern, bean, propertyNames), e);
}
}
//---------------------------------------------------------------
Map map = newLinkedHashMap(propertyNames.length);
for (String propertyName : propertyNames){
map.put(propertyName, getProperty(bean, propertyName));
}
return map;
}
//---------------------------------------------------------------
/**
* 使用 {@link PropertyUtils#setProperty(Object, String, Object)} 来设置指定bean对象中的指定属性的值.
*
* 说明:
*
*
* - 不会进行类型转换
*
*
*
* 示例:
*
*
*
*
* User newUser = new User();
* PropertyUtil.setProperty(newUser, "name", "feilong");
* LOGGER.info(JsonUtil.format(newUser));
*
*
* 返回:
*
*
* {
* "age": 0,
* "name": "feilong"
* }
*
*
*
*
* 注意点:
*
*
*
*
* - 如果
bean
是null,抛出 {@link NullPointerException}
* - 如果
propertyName
是null,抛出 {@link NullPointerException}
* - 如果
propertyName
是blank,抛出 {@link IllegalArgumentException}
* - 如果
bean
没有传入的 propertyName
属性名字,会抛出异常,see
* {@link com.feilong.lib.beanutils.PropertyUtilsBean#setSimpleProperty(Object, String, Object) setSimpleProperty} Line2078,转成
* {@link BeanOperationException}
* - 对于Date类型,不需要先注册converter
*
*
*
* @param bean
* Bean whose property is to be modified
* @param propertyName
* 属性名称 (can be nested/indexed/mapped/combo),参见 propertyName
* @param value
* Value to which this property is to be set
* @see com.feilong.lib.beanutils.BeanUtils#setProperty(Object, String, Object)
* @see com.feilong.lib.beanutils.PropertyUtils#setProperty(Object, String, Object)
* @see BeanUtil#setProperty(Object, String, Object)
*/
public static void setProperty(Object bean,String propertyName,Object value){
Validate.notNull(bean, MESSAGE_BEAN_IS_NULL);
Validate.notBlank(propertyName, MESSAGE_PROPERTYNAME_IS_BLANK);
//---------------------------------------------------------------
try{
PropertyUtils.setProperty(bean, propertyName, value);
}catch (Exception e){
String pattern = "setProperty exception,bean:[{}],propertyName:[{}],value:[{}]";
throw new BeanOperationException(Slf4jUtil.format(pattern, bean, propertyName, value), e);
}
}
//---------------------------------------------------------------
/**
* 如果 value
isNotNullOrEmpty,那么才调用 {@link #setProperty(Object, String, Object)}.
*
* 注意点:
*
*
*
* - 如果
bean
是null,抛出 {@link NullPointerException}
* - 如果
propertyName
是null,抛出 {@link NullPointerException}
* - 如果
propertyName
是blank,抛出 {@link IllegalArgumentException}
* - 如果
bean
没有传入的 propertyName
属性名字,会抛出异常,see
* {@link com.feilong.lib.beanutils.PropertyUtilsBean#setSimpleProperty(Object, String, Object)} Line2078
* - 对于Date类型,不需要先注册converter
*
*
*
* @param bean
* Bean whose property is to be modified
* @param propertyName
* 属性名称 (can be nested/indexed/mapped/combo),参见 propertyName
* @param value
* Value to which this property is to be set
* @since 1.5.3
*/
public static void setPropertyIfValueNotNullOrEmpty(Object bean,String propertyName,Object value){
if (isNotNullOrEmpty(value)){
setProperty(bean, propertyName, value);
}
}
/**
* 如果 null != value
,那么才调用 {@link #setProperty(Object, String, Object)}.
*
* 注意点:
*
*
*
*
* - 如果
bean
是null,抛出 {@link NullPointerException}
* - 如果
propertyName
是null,抛出 {@link NullPointerException}
* - 如果
propertyName
是blank,抛出 {@link IllegalArgumentException}
* - 如果
bean
没有传入的 propertyName
属性名字,会抛出异常,see
* {@link com.feilong.lib.beanutils.PropertyUtilsBean#setSimpleProperty(Object, String, Object)} Line2078
* - 对于Date类型,不需要先注册converter
*
*
*
* @param bean
* Bean whose property is to be modified
* @param propertyName
* 属性名称 (can be nested/indexed/mapped/combo),参见 propertyName
* @param value
* Value to which this property is to be set
* @see #setProperty(Object, String, Object)
* @since 1.5.3
*/
public static void setPropertyIfValueNotNull(Object bean,String propertyName,Object value){
if (null != value){
setProperty(bean, propertyName, value);
}
}
//---------------------------------------------------------------
// [start] getProperty
/**
* 使用 {@link PropertyUtils#getProperty(Object, String)} 从指定bean对象中取得指定属性名称的值.
*
* 说明:
*
*
* - 原样取出值,不会进行类型转换.
*
*
*
* 示例:
*
*
* 场景: 取list中第一个元素的id
*
*
*
* User user = new User();
* user.setId(5L);
* user.setDate(now());
*
* List{@code } list = toList(user, user, user);
*
* Long id = PropertyUtil.getProperty(list, "[0].id");
*
*
* 返回:
*
*
* 5
*
*
*
*
* @param
* the generic type
* @param bean
* Bean whose property is to be extracted
* @param propertyName
* 属性名称 (can be nested/indexed/mapped/combo),参见 propertyName
* @return 如果 bean
是null,抛出 {@link NullPointerException}
* 如果 propertyName
是null,抛出 {@link NullPointerException}
* 如果 propertyName
是blank,抛出 {@link IllegalArgumentException}
* 否则 使用{@link PropertyUtils#getProperty(Object, String)} 从对象中取得属性值
* @see BeanUtil#getProperty(Object, String)
* @see com.feilong.lib.beanutils.BeanUtils#getProperty(Object, String)
* @see com.feilong.lib.beanutils.PropertyUtils#getProperty(Object, String)
* @see com.feilong.lib.beanutils.PropertyUtilsBean
*/
public static T getProperty(Object bean,String propertyName){
Validate.notNull(bean, MESSAGE_BEAN_IS_NULL);
Validate.notBlank(propertyName, MESSAGE_PROPERTYNAME_IS_BLANK);
return PropertyValueObtainer.obtain(bean, propertyName);
}
//---------------------------------------------------------------
// [end]
/**
* 从指定的 obj
中,查找指定类型 toBeFindedClassType
的值.
*
* 说明:
*
*
*
* - 如果
ClassUtil.isInstance(obj, toBeFindedClassType)
直接返回 findValue
* - 不支持obj是
isPrimitiveOrWrapper
,CharSequence
,Collection
,Map
类型,自动过滤
* - 调用 {@link PropertyUtil#describe(Object, String...)} 再递归查找
* - 目前暂不支持从集合里面找到指定类型的值,如果你有相关需求,可以调用 "org.springframework.util.CollectionUtils#findValueOfType(Collection, Class)"
*
*
*
* 示例:
*
*
*
* 场景: 从User中找到UserInfo类型的值
*
*
*
* User user = new User();
* user.setId(5L);
* user.setDate(now());
* user.getUserInfo().setAge(28);
*
* LOGGER.info(JsonUtil.format(PropertyUtil.findValueOfType(user, UserInfo.class)));
*
*
* 返回:
*
*
* {"age": 28}
*
*
*
*
* @param
* the generic type
* @param obj
* 要被查找的对象
* @param toBeFindedClassType
* the to be finded class type
* @return 如果 obj
是null或者是empty,返回null
* 如果 toBeFindedClassType
是null,抛出 {@link NullPointerException}
* 如果 ClassUtil.isInstance(obj, toBeFindedClassType)
,直接返回 obj
* 从对象中查找匹配的类型,如果找不到返回 null
* @see "org.springframework.util.CollectionUtils#findValueOfType(Collection, Class)"
* @since 1.4.1
*/
@SuppressWarnings("unchecked")
public static T findValueOfType(Object obj,Class toBeFindedClassType){
if (isNullOrEmpty(obj)){
return null;
}
Validate.notNull(toBeFindedClassType, "toBeFindedClassType can't be null/empty!");
//---------------------------------------------------------------
if (ClassUtil.isInstance(obj, toBeFindedClassType)){
return (T) obj;
}
//---------------------------------------------------------------
if (isDonotSupportFindType(obj)){
LOGGER.trace("obj:[{}] not support find toBeFindedClassType:[{}]", obj.getClass().getName(), toBeFindedClassType.getName());
return null;
}
//---------------------------------------------------------------
Map describe = describe(obj);
for (Map.Entry entry : describe.entrySet()){
String key = entry.getKey();
Object value = entry.getValue();
if (null != value && !"class".equals(key)){
//级联查询
T t = findValueOfType(value, toBeFindedClassType);
if (null != t){
return t;
}
}
}
return null;
}
//---------------------------------------------------------------
/**
* 一般自定义的command 里面 就是些 string int,list map等对象.
*
*
* 这些我们过滤掉,只取类型是 findedClassType的
*
*
* @param obj
* the obj
* @return true, if checks if is can find type
* @since 1.6.3
*/
private static boolean isDonotSupportFindType(Object obj){
//一般自定义的command 里面 就是些 string int,list map等对象
//这些我们过滤掉,只取类型是 findedClassType的
return ClassUtils.isPrimitiveOrWrapper(obj.getClass())//
|| ClassUtil.isInstanceAnyClass(obj, CharSequence.class, Collection.class, Map.class);
}
}