Bean工具-BeanUtil
什么是Bean
把一个拥有对属性进行set和get方法的类,我们就可以称之为JavaBean。实际上JavaBean就是一个Java类,在这个Java类中就默认形成了一种规则——对属性进行设置和获得。而反之将说Java类就是一个JavaBean,这种说法是错误的,因为一个java类中不一定有对属性的设置和获得的方法(也就是不一定有set和get方法)。
通常Java中对Bean的定义是包含setXXX和getXXX方法的对象,在Hutool中,采取一种简单的判定Bean的方法:是否存在只有一个参数的setXXX方法。
Bean工具类主要是针对这些setXXX和getXXX方法进行操作,比如将Bean对象转为Map等等。
方法
是否为Bean对象
BeanUtil.isBean方法根据是否存在只有一个参数的setXXX方法或者public类型的字段来判定是否是一个Bean对象。这样的判定方法主要目的是保证至少有一个setXXX方法用于属性注入。
boolean isBean = BeanUtil.isBean(HashMap.class);//false
内省 Introspector
把一类中需要进行设置和获得的属性访问权限设置为private(私有的)让外部的使用者看不见摸不着,而通过public(共有的)set和get方法来对其属性的值来进行设置和获得,而内部的操作具体是怎样的?外界使用的人不用不知道,这就称为内省。
Hutool中对内省的封装包括:
BeanUtil.getPropertyDescriptors获得Bean字段描述数组PropertyDescriptor[] propertyDescriptors = BeanUtil.getPropertyDescriptors(SubPerson.class);
BeanUtil.getFieldNamePropertyDescriptorMap获得字段名和字段描述MapBeanUtil.getPropertyDescriptor获得Bean类指定属性描述Bean属性注入
BeanUtil.fillBean方法是bean注入的核心方法,此方法传入一个ValueProvider接口,通过实现此接口来获得key对应的值。CopyOptions参数则提供一些注入属性的选项。
CopyOptions的配置项包括:editable限制的类或接口,必须为目标对象的实现接口或父类,用于限制拷贝的属性,例如一个类我只想复制其父类的一些属性,就可以将editable设置为父类。ignoreNullValue是否忽略空值,当源对象的值为null时,true: 忽略而不注入此值,false: 注入nullignoreProperties忽略的属性列表,设置一个属性列表,不拷贝这些属性值ignoreError是否忽略字段注入错误
可以通过CopyOptions.create()方法创建一个默认的配置项,通过setXXX方法设置每个配置项。
ValueProvider接口需要实现两个方法:
value方法是通过key和目标类型来从任何地方获取一个值,并转换为目标类型,如果返回值不和目标类型匹配,将会自动调用Convert.convert方法转换。containsKey方法主要是检测是否包含指定的key,如果不包含这个key,其对应的属性将会忽略注入。
首先定义两个bean:
// Lombok注解@Datapublic class Person{private String name;private int age;}// Lombok注解@Datapublic class SubPerson extends Person {public static final String SUBNAME = "TEST";private UUID id;private String subName;private Boolean isSlow;}
然后注入这个bean:
Person person = BeanUtil.fillBean(new Person(), new ValueProvider<String>(){@Overridepublic Object value(String key, Class<?> valueType) {switch (key) {case "name":return "张三";case "age":return 18;}return null;}@Overridepublic boolean containsKey(String key) {//总是存在keyreturn true;}}, CopyOptions.create());Assert.assertEquals(person.getName(), "张三");Assert.assertEquals(person.getAge(), 18);
同时,Hutool还提供了BeanUtil.toBean方法,此处并不是传Bean对象,而是Bean类,Hutool会自动调用默认构造方法创建对象。
基于BeanUtil.fillBean方法Hutool还提供了Map对象键值对注入Bean,其方法有:
BeanUtil.fillBeanWithMap使用Map填充beanHashMap<String, Object> map = CollUtil.newHashMap();map.put("name", "Joe");map.put("age", 12);map.put("openId", "DFDFSDFWERWER");SubPerson person = BeanUtil.fillBeanWithMap(map, new SubPerson(), false);
BeanUtil.fillBeanWithMapIgnoreCase使用Map填充bean,忽略大小写HashMap<String, Object> map = CollUtil.newHashMap();map.put("Name", "Joe");map.put("aGe", 12);map.put("openId", "DFDFSDFWERWER");SubPerson person = BeanUtil.fillBeanWithMapIgnoreCase(map, new SubPerson(), false);
同时提供了map转bean的方法,与fillBean不同的是,此处并不是传Bean对象,而是Bean类,Hutool会自动调用默认构造方法创建对象。当然,前提是Bean类有默认构造方法(空构造),这些方法有:
BeanUtil.mapToBeanHashMap<String, Object> map = CollUtil.newHashMap();map.put("a_name", "Joe");map.put("b_age", 12);// 设置别名,用于对应bean的字段名HashMap<String, String> mapping = CollUtil.newHashMap();mapping.put("a_name", "name");mapping.put("b_age", "age");Person person = BeanUtil.mapToBean(map, Person.class, CopyOptions.create().setFieldMapping(mapping));
BeanUtil.mapToBeanIgnoreCaseHashMap<String, Object> map = CollUtil.newHashMap();map.put("Name", "Joe");map.put("aGe", 12);Person person = BeanUtil.mapToBeanIgnoreCase(map, Person.class, false);
Bean转为Map
BeanUtil.beanToMap方法则是将一个Bean对象转为Map对象。SubPerson person = new SubPerson();person.setAge(14);person.setOpenid("11213232");person.setName("测试A11");person.setSubName("sub名字");Map<String, Object> map = BeanUtil.beanToMap(person);
Bean转Bean
Bean之间的转换主要是相同属性的复制,因此方法名为
copyProperties,此方法支持Bean和Map之间的字段复制。BeanUtil.copyProperties方法同样提供一个CopyOptions参数用于自定义属性复制。SubPerson p1 = new SubPerson();p1.setSlow(true);p1.setName("测试");p1.setSubName("sub测试");Map<String, Object> map = MapUtil.newHashMap();BeanUtil.copyProperties(p1, map);
Alias注解
5.x的Hutool中增加了一个自定义注解:
Alias,通过此注解可以给Bean的字段设置别名。
首先我们给Bean加上注解:// Lombok注解@Getter@Setterpublic static class SubPersonWithAlias {@Alias("aliasSubName")private String subName;private Boolean slow;}
SubPersonWithAlias person = new SubPersonWithAlias();person.setSubName("sub名字");person.setSlow(true);// Bean转换为Map时,自动将subName修改为aliasSubNameMap<String, Object> map = BeanUtil.beanToMap(person);// 返回"sub名字"map.get("aliasSubName")
同样Alias注解支持注入Bean时的别名:
Map<String, Object> map = MapUtil.newHashMap();map.put("aliasSubName", "sub名字");map.put("slow", true);SubPersonWithAlias subPersonWithAlias = BeanUtil.mapToBean(map, SubPersonWithAlias.class, false);// 返回"sub名字"subPersonWithAlias.getSubName();
DynaBean
介绍
DynaBean是使用反射机制动态操作JavaBean的一个封装类,通过这个类,可以通过字符串传入name方式动态调用get和set方法,也可以动态创建JavaBean对象,亦或者执行JavaBean中的方法。
使用
我们先定义一个JavaBean:
// Lombok注解@Datapublic static class User{private String name;private int age;public String testMethod(){return "test for " + this.name;}}
创建
DynaBean bean = DynaBean.create(user);//我们也可以通过反射构造对象DynaBean bean2 = DynaBean.create(User.class);
操作
我们通过DynaBean来包装并操作这个Bean
User user = new User();DynaBean bean = DynaBean.create(user);bean.set("name", "李华");bean.set("age", 12);String name = bean.get("name");//输出“李华”
invoke
除了标准的get和set方法,也可以调用invoke方法执行对象中的任意方法:
//执行指定方法Object invoke = bean2.invoke("testMethod");Assert.assertEquals("test for 李华", invoke);
说明: DynaBean默认实现了hashCode、equals和toString三个方法,这三个方法也是默认调用原对象的相应方法操作。
表达式解析-BeanPath
由来
很多JavaBean嵌套有很多层对象,这其中还夹杂着Map、Collection等对象,因此获取太深的嵌套对象会让代码变得冗长不堪。因此我们可以考虑使用一种表达式还获取指定深度的对象,于是BeanResolver应运而生。
原理
通过传入一个表达式,按照表达式的规则获取bean下指定的对象。
表达式分为两种:
.表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值[]表达式,可以获取集合等对象中对应index的值
栗子:
persion获取Bean对象下person字段的值,或者Bean本身如果是Person对象,返回本身。persion.name获取Bean中person字段下name字段的值,或者Bean本身如果是Person对象,返回其name字段的值。persons[3]获取persons字段下第三个元素的值(假设person是数组或Collection对象)person.friends[5].name获取person字段下friends列表(或数组)的第5个元素对象的name属性使用
由于嵌套Bean定义过于复杂,在此我们省略,有兴趣的可以看下这里:cn.hutool.core.lang.test.bean(src/test/java下)下定义了测试用例用的bean。
首先我们创建这个复杂的Bean(实际当中这个复杂的Bean可能是从数据库中获取,或者从JSON转入)
这个复杂Bean的关系是这样的:
定义一个Map包含用户信息(UserInfoDict)和一个标志位(flag),用户信息包括一些基本信息和一个考试信息列表(ExamInfoDict)。
下面,我们使用//------------------------------------------------- 考试信息列表ExamInfoDict examInfoDict = new ExamInfoDict();examInfoDict.setId(1);examInfoDict.setExamType(0);examInfoDict.setAnswerIs(1);ExamInfoDict examInfoDict1 = new ExamInfoDict();examInfoDict1.setId(2);examInfoDict1.setExamType(0);examInfoDict1.setAnswerIs(0);ExamInfoDict examInfoDict2 = new ExamInfoDict();examInfoDict2.setId(3);examInfoDict2.setExamType(1);examInfoDict2.setAnswerIs(0);List<ExamInfoDict> examInfoDicts = new ArrayList<ExamInfoDict>();examInfoDicts.add(examInfoDict);examInfoDicts.add(examInfoDict1);examInfoDicts.add(examInfoDict2);//------------------------------------------------- 用户信息UserInfoDict userInfoDict = new UserInfoDict();userInfoDict.setId(1);userInfoDict.setPhotoPath("yx.mm.com");userInfoDict.setRealName("张三");userInfoDict.setExamInfoDict(examInfoDicts);Map<String, Object> tempMap = new HashMap<String, Object>();tempMap.put("userInfo", userInfoDict);tempMap.put("flag", 1);
BeanPath获取这个Map下此用户第一门考试的ID:
只需两句(甚至一句)即可完成复杂Bean中各层次对象的获取。BeanPath resolver = new BeanPath("userInfo.examInfoDict[0].id");Object result = resolver.get(tempMap);//ID为1
说明: 为了简化
BeanPath的使用,Hutool在BeanUtil中也加入了快捷入口方法:BeanUtil.getProperty,这个方法的命名更容易理解(毕竟BeanPath不但可以解析Bean,而且可以解析Map和集合)。
