背景
相信大家在日常的工作中,一定遇到过以下的某个场景:
- 前端需要选择某些字段,去展示不同字段下的信息,如果在字段和方法没有绑定的情况下,如何调用get方法呢?
- 如果有一张横转纵的表,存储的do都是实际的字段名称,那么如何转化成实体类对应的get、set方法去执行操作呢?
- 如果我想根据方法名称去调用对象的某个方法呢?
相信有些基础的程序员会立刻想到使用反射就好了,没错,就是这么简单,但是每用到一次,咱们就去写一次也是比较麻烦的,所以我们可以将它封装成工具类,用的时候直接去调用就好了。
实现
目前我在工具类实现了三个方法,分别是:
- 根据属性名调用对象get方法
- 根据属性名调用对象的set方法
- 根据方法名调用方法
根据属性名调用对象get方法
实现逻辑:
- 获取对象的class
- 获取对象的所有属性
- 获取对象的声明方法
- 遍历属性
- 匹配方法
- invoke执行该方法
- 返回方法的返回值
/** * 根据属性名获取属性值 * * @return java.lang.String * @Param filedName * @Param obj * @Date 2022/12/15 15:06 * @Author wjbgn **/ public static String getObjField(String filedName, Object obj) { AtomicReference<String> value = new AtomicReference<>(); Class<?> aClass = obj.getClass(); // 获取所有属性 Field[] declaredFields = aClass.getDeclaredFields(); // 获取所有方法 Method[] declaredMethods = aClass.getDeclaredMethods(); Arrays.stream(declaredFields).forEach(filed -> { if (filed.getName().equals(filedName)) { // 属性存在,尝试获取属性,调用get方法,此处get方法需要组装 Arrays.stream(declaredMethods).forEach(method -> { if (method.getName().toLowerCase().equals("get" + filedName)) { try { // 执行方法 Object invoke = method.invoke(obj); value.set(invoke == null ? null : invoke.toString()); } catch (Exception e) { throw new RuntimeException(e); } } }); } }); return value.get(); }
根据属性名调用对象的set方法
实现逻辑:
- 获取对象的声明方法
- 遍历方法
- 匹配方法名称
- invoke执行该方法
/** * 根据属性名设置属性值 * * @return void * @Param filedName 字段名 * @Param value 字段值 * @Param obj 对象 * @Date 2022/12/15 14:41 * @Author wjbgn **/ public static void setObjField(String filedName, String value, Object obj) { Class<?> aClass = obj.getClass(); Arrays.stream(aClass.getDeclaredMethods()).forEach(method -> { if (method.getName().toLowerCase().equals("set" + filedName)) { try { method.invoke(obj, value); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } }); }
根据方法名调用方法
实现逻辑:
- 获取对象的所有方法 -> 不同于获取get、set,此处需要获取所有的方法,包括实现和继承来的。
- 遍历方法
- 匹配方法名称
- invoke执行该方法
- 返回方法返回值
/** * 根据方法名调用该方法 * * @return java.lang.Object * @Param filedName * @Param obj * @Date 2022/12/15 15:05 * @Author wjbgn **/ public static Object invokeMethod(String filedName, Object obj) { AtomicReference<Object> result = new AtomicReference<>(); Arrays.stream(obj.getClass().getMethods()).forEach(method -> { if (method.getName().toLowerCase().contains(filedName)) { // 有方法包含该属性 try { Object invoke = method.invoke(obj); result.set(invoke); return; } catch (Exception e) { throw new RuntimeException(e); } } }); return result.get(); }
测试
准备基础代码
使用一段代码来测试下我们的方法,首先准备一些基础类。
背景是有三个小学生,分别是詹姆斯,库里,杜兰特,每个人共有一些属性,如下所示:
/** * 学生类,每个学生可以跑,跳,投篮 */ static class Student implements PlayerActon { private String name; private String age; private String team; @FieldDesc(type = "exclusive", value = " learn exclusive skills >> ") private String exclusive; @FieldDesc(value = "'s phone num? I don't know!") private String phone; public Student(String name, String age, String team, String phone) { this.name = name; this.age = age; this.team = team; this.phone = phone; } public Student() { } public String getExclusive() { return exclusive; } public void setExclusive(String exclusive) { this.exclusive = exclusive; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } public String getTeam() { return team; } public void setTeam(String team) { this.team = team; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Override public String running() { return " is running!"; } @Override public String jumping() { return " is jumping!"; } @Override public String shooting() { return " make a shot!"; } }
上面的实体类实现了一个接口PlayerActon,里面是三个方法,表示运动员可以跑,跳,投篮:
/** * 动作接口 */ private interface PlayerActon { /** * 跑 */ String running(); /** * 跳 */ String jumping(); /** * 投篮 */ String shooting(); }
除此之外,看到下面的两个属性,分别带有一个注解:
@FieldDesc(type = "exclusive", value = " learn exclusive skills >> ") private String exclusive; @FieldDesc(value = "'s phone num? I don't know!") private String phone;
这里没什么别的含义,就是想在反射的时候,给这个属性赋默认值,在注解上面可以直接取值,比较方便。另外注解的属性还有一个type,这个type用来指定当前的属性是专属字段,因为不同的球员有不同的个性,我们通过这个类型判断下,如果是这个字段,那么要给上面的三个小学生赋不同的专属技能了:
/** * 自定义注解,描述字段 */ @Documented @Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) private @interface FieldDesc { /** * 类型 */ String type() default ""; /** * 字段描述 */ String value() default ""; }复制代码
既然说到了技能了,那就把技能枚举定义一下:
/** * 技能枚举 */ public static enum SkillsEnum { STAND_HAND("James", "STAND HAND!!!", "摊手"), SHAKE_HEAD("Curry", "SHAKE HEAD!!!", "摇头"), SHAKE_SHOULDERS("Durant", "SHAKE SHOULDERS!!!", "晃肩膀"); private String studentName; private String skillsName; private String skillsNameDesc; SkillsEnum(String studentName, String skillsName, String skillsNameDesc) { this.studentName = studentName; this.skillsName = skillsName; this.skillsNameDesc = skillsNameDesc; } public String getStudentName() { return studentName; } public void setStudentName(String studentName) { this.studentName = studentName; } public String getSkillsName() { return skillsName; } public void setSkillsName(String skillsName) { this.skillsName = skillsName; } public String getSkillsNameDesc() { return skillsNameDesc; } public void setSkillsNameDesc(String skillsNameDesc) { this.skillsNameDesc = skillsNameDesc; } /** * 根据学生获取技能 * * @return java.lang.String * @Param student * @Date 2022/12/15 14:21 * @Author wjbgn **/ public static String getSkillsByStudent(Student student) { for (SkillsEnum skillsEnum : SkillsEnum.values()) { if (skillsEnum.getStudentName().equals(student.getName())) { return skillsEnum.getSkillsName(); } } return null; } }
准备main方法
下面我们准备一个main方法,模拟一个场景:
/*** * 工具类测试样例 * * @Param args * @return void * @Date 2022/12/15 15:10 * @Author wjbgn **/ public static void main(String[] args) { // 获取动作对应的结果,循环10次 for (int i = 0; i < 10; i++) { try { // 随机获取一个学生 Student student = studentList.get(new Random().nextInt(3)); // 随机获取一个动作 String action = actionList.get(new Random().nextInt(7)); // 打印下随机结果 System.out.println(getStudentField(action, student)); // Thread.sleep(500L); } catch (Exception e) { throw new RuntimeException(e); } } }
如上所示,循环10次,分别调用getStudentField方法,方法后面会讲,这个方法就是为了获取学生的属性,但是我们从上面的代码看的出来,获取哪一个学生,获取学生的哪一个属性都是随机的,所以我们首先把这些属性和学生初始化一下,其中除了有字段属性,还有方法名称:
/** * 动作集合 */ private static List<Student> studentList = new ArrayList<>(); /** * 动作集合 */ private static List<String> actionList = new ArrayList<>(); /** * 初始化 动作集合,学生 * 这里面都使用字段的名称,不使用get、set方法 */ static { // 获取球员的年龄 actionList.add("age"); // 获取球队 actionList.add("team"); // 获取电话 actionList.add("phone"); // 学习/使用专属动作 actionList.add("exclusive"); // 跑 actionList.add("running"); // 跳 actionList.add("jumping"); // 投篮 actionList.add("shooting"); studentList.add(new Student("James", " 37 years old", " From the Los Angeles Lakers", "")); studentList.add(new Student("Curry", " 34 years old", " From the Golden State Warriors", "")); studentList.add(new Student("Durant", " 33 years old", " From the Brooklyn Nets", "")); }
有了上面的初始化,我们就可以随机的调用getStudentField方法,步骤:
- 首先将学生名字返回拼接到字符串
- 通过属性名称和学生对象调用前面封装好的ObjectDynamicUtil.getObjField方法
- 如果没获取到属性,表示属性为空或者不是属性,是方法
- 去调用setStudentField 方法,如果返回有值,则成功,再次ObjectDynamicUtil.getObjField获取一次
- 如果仍然是空,那么就调用前面封装好的ObjectDynamicUtil.invokeMethod,按属性调用方法。
- 返回结果
/*** * 动态获取学生属性 * * @Param * @return void * @Date 2022/12/15 11:23 * @Author wjbgn **/ private static String getStudentField(String filedName, Student student) throws NoSuchFieldException { String msg = student.getName(); // 获取属性值 String value = ObjectDynamicUtil.getObjField(filedName, student); if (value == null || value == "") { // 如果获取属性是空怎么办?设置一个值进去 setStudentField(filedName, student); // 设置值后,再次执行get方法 value = ObjectDynamicUtil.getObjField(filedName, student); } // 调用学生实现的动作接口方法 if (value == null) { value = (String) ObjectDynamicUtil.invokeMethod(filedName, student); } msg += value; return msg; }
下面看下设置学生属性值的方法:setStudentField,步骤:
- 获取学生对象class
- 根据属性名获取class的属性
- 根据属性获取注解FieldDesc,即前面自定义的注解
- 如果注解类型是exclusive,就根据学生从枚举类获取专属技能
- 拼装结果并调用ObjectDynamicUtil.setObjField设置对象属性
/*** * 动态设置学生属性 * * @Param * @return void * @Date 2022/12/15 11:23 * @Author wjbgn **/ private static void setStudentField(String filedName, Student student) throws NoSuchFieldException { Class<? extends Student> studentClass = student.getClass(); Field declaredField = null; try { declaredField = studentClass.getDeclaredField(filedName); } catch (NoSuchFieldException e) { return; } catch (SecurityException e) { throw new RuntimeException(e); } FieldDesc annotation = declaredField.getAnnotation(FieldDesc.class); String value = annotation.value(); String finalValue = value + (annotation.type().equals(filedName) ? SkillsEnum.getSkillsByStudent(student) : ""); ObjectDynamicUtil.setObjField(filedName, finalValue, student); }
查看结果
到此为止,所有的代码都准备完毕了,记得把main方法的Thread。sleep注释放开,看到的结果更加直观。
此处注释是因为在码上掘金导致代码不能运行,不知道码上掘金是什么原因?
结果如下图所示:
如上所示,看到不同的学生可以做不同的事,展示不同的属性,都是随机动态获取的。
总结
反射是java中,最基础,也是最核心的内容,同样也是最有用的。然而实际的工作当中,我们接触到的机会少之又少,所以我们需要自我提升,将这些手段融会贯通。本文涉及的知识很小一部分反射知识,但是对应经常与表单,表格打交道的后端程序员来说,却非常有用,赶紧用起来吧~~
版权声明:内容来源于互联网和用户投稿 如有侵权请联系删除