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

背景

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

相信有些基础的程序员会立刻想到使用反射就好了,没错,就是这么简单,但是每用到一次,咱们就去写一次也是比较麻烦的,所以我们可以将它封装成工具类,用的时候直接去调用就好了。

实现

目前我在工具类实现了三个方法,分别是:

根据属性名调用对象get方法

实现逻辑:

/**
         * 根据属性名获取属性值
         *
         * @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 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方法

实现逻辑:

/**
         * 根据属性名设置属性值
         *
         * @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);
                    }
                }
            });
        }

根据方法名调用方法

实现逻辑:

/**
        * 根据方法名调用该方法
        *
        * @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 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 studentList = new ArrayList<>();

   /**
    * 动作集合
    */
   private static List 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中,最基础,也是最核心的内容,同样也是最有用的。然而实际的工作当中,我们接触到的机会少之又少,所以我们需要自我提升,将这些手段融会贯通。本文涉及的知识很小一部分反射知识,但是对应经常与表单,表格打交道的后端程序员来说,却非常有用,赶紧用起来吧~~

页面更新:2024-04-02

标签:注解   字段   反射   属性   对象   技能   名称   动作   代码   方法   学生

1 2 3 4 5

上滑加载更多 ↓
Top