简简单单的反射和詹杜库放在一起就能好玩了吗?

发布一下 0 0

背景

相信大家在日常的工作中,一定遇到过以下的某个场景:

  • 前端需要选择某些字段,去展示不同字段下的信息,如果在字段和方法没有绑定的情况下,如何调用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中,最基础,也是最核心的内容,同样也是最有用的。然而实际的工作当中,我们接触到的机会少之又少,所以我们需要自我提升,将这些手段融会贯通。本文涉及的知识很小一部分反射知识,但是对应经常与表单,表格打交道的后端程序员来说,却非常有用,赶紧用起来吧~~

版权声明:内容来源于互联网和用户投稿 如有侵权请联系删除

本文地址:http://0561fc.cn/194254.html