# BeanUtil **Repository Path**: bilbodai/BeanUtil ## Basic Information - **Project Name**: BeanUtil - **Description**: using asm to enhance the performance of extract property of java bean - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 0 - **Created**: 2015-08-09 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #BeanUtil Fast Way For Handling Bean Properties! ``` net.oschina.bilbodai.common.beanutil beanutil 3.1.1 ``` ## PropertyUtil ### Quick Start ``` public class AbstractFoo { private String type; //getters & setters } public class Foo extends AbstractFoo{ private String name; private Map fooMap; private Foo[] foos; private List fooList; private Foo buddy; private Map simpleMap; //getters & setters } ``` #### Simple Query Property能够对声明的类型进行自动推断,下面几个样例解释声明类型的使用。**注意:**需要给属性提供getter方法。 ###### Simple Path Query 简单递归查询,通过『.』连接属性即可。 ``` assertEquals(foo.getBuddy().getName(),PropertyUtil.getProperty("buddy.name",foo)); ``` ###### Array Path Query 数组查询,通过数组类型属性名+『[0]』得到数组中小标为0的值,Property会自动推断类型。 ``` foo.setFoos(new Foo[]{foo}); assertEquals(foo.getFoos()[0].getName(),PropertyUtil.getProperty("foos[0].name",foo)); ``` ##### List Path Query 同数组查询 ``` List list = new ArrayList<>(); list.add(foo); foo.setFooList(list); assertEquals(foo.getFooList().get(0).getName(), PropertyUtil.getProperty("fooList[0].name", foo)); ``` ##### Map Path Query Map查询,通过Map类型属性名+『.』+键名得到Map键所在的值,Property会自动推断类型。 ``` Map map = new HashMap<>(); map.put("foo", foo); foo.setFooMap(map); assertEquals(foo.getFooMap().get("foo").getName(), PropertyUtil.getProperty("fooMap.foo.name", foo)); ``` #### Typed Query 说完了自动推断,来说说Property无法推断类型的时候如何处理。 ##### Map Typed Query ``` Map map = new HashMap(); map.put("foo", foo); foo.setSimpleMap(map); Foo expected = (Foo) this.foo.getSimpleMap().get("foo"); assertEquals(expected.getName(),PropertyUtil.getProperty("simpleMap.foo.name", this.foo)); ``` 这里有个有趣的标识``和代码中一样,通过`<,>`来分割键类型和值类型,键类型只支持以下类型: ``` "byte" "short" "int" "long" "float" "double" "string" "boolean" ``` `@net/oschina/bilbodai/common/beanutil/property/test/Foo`中的`@`后接类名,注意将`.`替换为`/`形式。 如果觉得这种方式不方便,可以调用`ValueTypeMappingFactory.bindType("fooType",Foo.class);`进行类型绑定,绑定之后使用就可以用`#fooType`告诉Property进行类型推断。 ``` ValueTypeMappingFactory.bindType("fooType",Foo.class); Map map = new HashMap(); map.put("foo", foo); foo.setSimpleMap(map); Foo expected = (Foo) this.foo.getSimpleMap().get("foo"); assertEquals(expected.getName(),PropertyUtil.getProperty("simpleMap.", this.foo)); ``` 这种方式也有个缺点就是很可能出现绑定的key的冲突,所以也支持namespace的方式。 ``` ValueTypeMappingFactory.registerValueTypeMapping("foo", new IValueTypeMapping() { @Override public Class findTypeByName(String name) { if (name.equals("fooType")) { return Foo.class; } return null; } }); Map map = new HashMap(); map.put("foo", foo); foo.setSimpleMap(map); Foo expected = (Foo) this.foo.getSimpleMap().get("foo"); assertEquals(expected.getName(),PropertyUtil.getProperty("simpleMap.", this.foo)); ``` 调用`ValueTypeMappingFactory.registerValueTypeMapping`将一个namespace和`IValueTypeMapping`绑定。 ##### List Type Query ``` List list = new ArrayList(); list.add(foo); assertEquals(((Foo)list.get(0)).getName(),PropertyUtil.getProperty("[0<@net/oschina/bilbodai/common/beanutil/property/test/Foo>].name",list)); ``` `[0<@net/oschina/bilbodai/common/beanutil/property/test/Foo>]`只需要在`[<>]`像类似MapTypeQuey那样指定类型,方式通MapTypeQuery类型绑定方式一样。 **注意:** - 当Path中间任意属性值获取为null时,整个query将返回最终字段类型的默认值。如 ``` assertNull(PropertyUtil.getProperty("buddy.name",foo));//如果buddy为null ``` - PropertyUtil会根据path和类型信息缓存实时生成的Query字节码信息,也就是说 `PropertyUtil.getProperty("buddy.name",foo)` 会生成一个类似 `return foo.getBuddy().getName()` 的字节码。所以获取速度很快。 - 如果希望字节码在应用启动的时候就生成,那么只需要调用 `Property PropertyUtil.make(String propertyPath, final Class beanType)` 得到 `Property` 实例。 ## BeanDumpUtil ## Quickstart ``` class FooBody{ private int[] bits; //getters & setters } class Foo { private String name; private String desc; private int age; private Date date; private boolean done; private FooBody body; private Set nameSet; private List names; private Set ages; private List eles; private SingleFooEle ele; private SimpleFoo mirror; //getters & setters } class FooTarget { private String name; private String desc; private int age; private boolean done; private Date time; private Integer bit; @DumpProperty(targetType = PropertyDumpType.LIST, targetComponentType = FooCollectEleTarget.class) private List nameSet; @DumpProperty(targetType = PropertyDumpType.SET, targetComponentType = FooCollectEleTarget.class, srcComponentType = FooCollectEle.class, srcType = PropertyDumpType.LIST, name = "names") private Set eleTargetSet; @DumpProperty(targetType = PropertyDumpType.IT_LIST) private List ages; @DumpProperty(targetType = PropertyDumpType.IT_LIST, name = "eles", attrPath = "name") private List eleNames; @DumpProperty(name = "ele",targetType = PropertyDumpType.SINGLE) private SingleFooEleTarget eleTarget; @DumpProperty(name = "mirror",attrPath = "name") private String mirrorName; //getters & setters } ``` 调用`BeanDumpUtil.propertyDump(foo);`可以很轻松的完成对两个对象的属性拷贝。 ``` fooTarget = BeanDumpUtil.propertyDump(foo); assertEquals(foo.getName(), fooTarget.getName()); assertEquals(foo.getDesc(), fooTarget.getDesc()); assertEquals(foo.getAge(), fooTarget.getAge()); assertEquals(foo.getDate(),fooTarget.getTime()); assertTrue(fooTarget.isDone()); assertEquals(foo.getBody().getBits()[0],fooTarget.getBit().intValue()); ``` PropertyDumpType有以下几种类型: - `REFERENCE` 当需要把源实例中的属性直接拷贝到目标实例对应的字段时使用这个类型,这个也是**默认**的类型。**注意:** 要求目标字段类型必须能被源字段类型赋值。也就是类型要匹配。 - `SINGLE` 当实例中某个字段是一个复杂的类型,但是又不和目标字段匹配时。 ``` //src private SingleFooEle ele; //target @DumpProperty(name = "ele",targetType = PropertyDumpType.SINGLE) private SingleFooEleTarget eleTarget; ``` 只需要将类型改为`SINGLE`, BeanDumpUtil就能递归拷贝`SingleFooEle`的属性到`SingleFooEleTarget`。当然,可能你只需要其中的某个属性 只需要加上`attrPath = "要拷贝的字段名"`即可。 ``` @DumpProperty(name = "mirror",attrPath = "name") private String mirrorName; ``` - `IT_LIST` 说完了SINGLE,下面的就很好理解了,`IT_LIST`就是用在源字段和目标字段都是集合类型的时候,把源字段集合中元素遍历然后加入到目标字段中。 如上面`private Set ages; -> private List ages;` - `IT_SET` 这个类型和上面的一样。只是标识目标字段是Set集合,方便BeanDumpUtil在无法进行类型推断的时候使用。 - `LIST` 和`IT_LIST`很相似,区别就在于源字段集合和目标字段集合的元素类型不兼容。比如` List -> List`BeanDumpUtil会遍历源字段每个元素并且把元素的字段递归拷贝到目标元素中再加入到集合里。 - `SET` 这个类型和上面的`LIST`一样。只是标识目标字段是Set集合,方便BeanDumpUtil在无法进行类型推断的时候使用。 - `MAP` 用于源和目标Map中值类型不一致时 - `IT_MAP` 用于源和目标Map中值类型一致时 当然也可以在注解中手动指定源实例和目标实例的字段类型: ``` /** * @return 源集合的元素类型, Collection中指元素的类型, 一般用于字段没有显示声明泛型的情况 */ Class srcComponentType() default NullType.class; /** * @return 目标集合的元素类型, Collection中指元素的类型, Map中指值类型, 一般用于字段没有显示声明泛型的情况 */ Class targetComponentType() default NullType.class; /** * @return 目标集合的实现类, 如果未指定, 那么采用 {@link PropertyDumpType#typeImpl} */ Class collectTypeImpl() default NullType.class; ``` 例如上面的: ``` @DumpProperty(targetType = PropertyDumpType.SET, targetComponentType = FooCollectEleTarget.class, srcComponentType = FooCollectEle.class, srcType = PropertyDumpType.LIST, name = "names") private Set eleTargetSet; ``` 就是在告诉BeanDumpUtil将源`name`字段集合中的类型为`FooCollectEle.class`元素挨个迭代转换成`FooCollectEleTarget.class`并入`LinkedHashSet`集合中并且赋值给`eleTargetSet`字段。 ##### 配置 BeanUtil寻找的是Target对象的setter来匹配Source对象的getter,意思就是setXX会匹配getXX同时还要保证二者类型是一致的即可无需任何配置完成属性值拷贝。当然也可能有特殊情况。 - 名称不一致 ``` Foo { @DumpProperty(name = "time") private Date date; FooTarget { private Date time; } ``` 出现名称不一致的时候可以通过`@DumpProperty(name = "time")`在源对象的字段上标识或者在目标对象的字段上`@DumpProperty(name = "date")`进行标识。二者选一即可。 - 过滤 ``` @DumpIgnore private boolean done; ``` 不想让某个属性参与属相拷贝,在目标属性上标识`@DumpIgnore`即可 - 替换 ``` @DumpProperty(forceDump=true) private int age; ``` BeanUtil建议进行属性拷贝的对象的属性类型都是引用类型,这样BeanUtil会在将源拷贝到目标时对目标属性的值进行`Null Check`, 意思就是目标属性为空的时候才会进行属性拷贝,当然原始类型没办法进行`Null Check`所以,在原始类型上必须加上`@DumpProperty(forceDump=true)`让BeanUtil不进行Check。当然引用类型需要无论如何都替换的时候也可以加上 - 利用PropertyUtil进行Path Query ``` @DumpProperty(name="body", attrPath = "bits[0]", attrType=Integer.class) private Integer bit; ``` 当然目标属性也可以取源复杂属性中的一个,通过`@DumpProperty(name="body", attrPath = "bits[0]", attrType=Integer.class)`中用`bits[0]`来告诉BeanUtil进行`Property Path Quey`, 请参见PropertyUtil小节。 `@DumpProperty(attrPath = "body.bits[0]")`需要通过`attrType`指定字段的类型。 ## BeanDumpUtil(2.3.0)重要更新 - 修改了注解名(`@PropertyDump` -> `@DumpProperty`, `@IgnoreDump` -> `@DumpIgnore`) - 删除了`queryPath`配置,因为`name`和`attrPath`配合可以达到同样的目的 - 增加了`@DumpProperties`和`@DumpProperties`注解,当类A,B,C中, A->dump->B, B->dump->C时,而A和C要共用B中某个字段,然而配置不一样时,即可通过`@DumpProperties`进行多项配置,其中`peerTypes`表示与之dump的类 - 增加了自动`DumpType`推导功能 - 增加了Map转Collection,和Collection转Map功能 ### 自动`DumpType`推导功能 - 如果源字段类型可以直接赋值给目标字段类型但不是集合类型,那么采用直接赋值`REFERENCE` - 如果源字段类型可以直接赋值给目标字段类型是集合类型 - 如果是Collection类型,但元素类型不一致,那么对每个元素进行递归Dump,`IT_LIST`或者`IT_SET` - 如果是Map类型,但Value类型不一致,那么对每个value进行递归dump,`IT_MAP` - 如果元素类型或者Value类型一致,那么采用直接复制`REFERENCE` - 如果源字段类型不能直接赋值给目标字段类型但不是集合类型,那么直接采用递归Dump`IT_IT` - 如果源字段类型不能直接赋值给目标字段类型但是集合类型 - SET转LIST或者LIST转SET,那么遍历源每个元素递归Dump放入目标集合`SET`或者`LIST` - Collection转Map,元素类型不一致时,遍历源每个元素递归Dump放入目标集合,一致时直接放入 - Map转Collection时,元素类型不一致时,递归Dump,一致时直接放入 ### Map转Collection - 1) ``` private Map fooMap; -> @DumpProperty(name = "fooMap") private List fooList; ``` 遍历fooMap中的每一个Entry,取MFoo实例 dump 成MTFoo,然后放入fooList - 2) ``` private Map fooMap; -> @DumpProperty(name = "fooMap") private List fooList; ``` 遍历fooMap中的每一个Entry,取MFoo实例后直接放入fooList ### Collection转Map - 1) ``` private List fooList; -> @DumpProperty(name = "fooList", mapFieldName = "name") private Map nameToFooMap; ``` *注意* Collection转Map时`mapFieldName`必须指定,如上,也就是使用MFoo的name字段值作为key。 把MFoo的name字段取出来作为key,MFoo dump 成MTFoo作为value放入nameToFooMap中 - 2) ``` private List fooList; -> @DumpProperty(name = "fooList", mapFieldName = "name") private Map nameToFooMap; ``` *注意* Collection转Map时`mapFieldName`必须指定,如上,也就是使用MFoo的name字段值作为key。 把MFoo的name字段取出来作为key,MFoo直接作为value放入nameToFooMap中 ## BeanUtil(3.0.2)重要更新-增加PropertyMapping 什么是PropertyMapping?来看下面的测试代码,Foo类型中包含了几个字段,其中一个是复杂类型`Bar`。某些场景我们可能需要将一个层次结构的Bean通过特殊的键映射将需要的字段值扁平化到某个Map中。如下面的例子,我们需要完成如下映射: | 属性路径 | key | value | | :-------- | --------:| :------: | | Foo.name | name | "foo" | | Foo.age | fooAge | 19 | | Foo.bar.name | bar.name | "bar" | | Foo.bar.age | barAge | 20 | ``` public static class Foo{ private String name = "foo"; @Mapped(id = "fooAge") private int age = 19; private Bar bar = new Bar(); public String getName() { return name; } public int getAge() { return age; } public Bar getBar() { return bar; } } public static class Bar{ private String name = "bar"; @Mapped(id = "barAge", additivity = false) private int age = 20; @MapIgnore private String ignore = "ignore"; public String getIgnore() { return ignore; } public String getName() { return name; } public int getAge() { return age; } } ``` 通过上面的映射规则,很容易通过代码实现: ``` public Map mapTo(Foo foo){ ... map.put("name",foo.getName()) ... ... return map; } ``` 但是这样你就不得某个字段修改后去维护这个mapTo逻辑,并且很容易出错。所以BeanUtil提供一种更简便的方式: ``` PropertyMapping propertyMapping = PropertyMappings.create(Foo.class); Foo foo = new Foo(); Map map = propertyMapping.from(foo); ``` - BeanUtil 默认会把所有的字段作为mapping的字段并且用字段名作为路径节点名 - 如果不想将字段作为mapping字段,那么加上`@MapIgnore`即可 - 如果需要修改map节点名,那么需要加上`@Mapped`其中`id`表示节点名,`additivity`为true表示这个节点名不沿用外层节点 这样任何字段改了,不需要做任何map逻辑的修改。更重要的是,它的效率和手写map实现是一样的。这是因为它进行了字节码增强。 ## BeanUtil(3.0.5) - 增加String转枚举和枚举转String的特性 ## BeanUtil(3.0.6-3.0.7) - 增加对集合(Collection)类型的map支持 只需要在需要的字段中加入注解,即可 ``` @MappedCollection(keyPath = "slice", mode = CollectionMappingMode.AS_ONE) private List slices = Arrays.asList(new Slice("s1"), new Slice("s2")); ``` 对于集合字段的处理有三种模式: ``` /** * 忽略集合的mapping,及所有集合类型的字段都不加入map中 */ NONE(0), /** * 将集合作为一个整体,加入到map中 */ AS_ONE(1), /** * 递归集合的类型, 注意这样会根据集合的个数产生很多key */ RECURSIVE(2); ``` 如果字段中没有注解标识mode,那么可以在创建的时候指定全局mode ``` PropertyMapping create(final Class targetType, CollectionMappingMode globalCollectMapMode) ```