关于java:Java反射真正的详解学以致用实战案例虽不及万字但也已有近8000字

40次阅读

共计 27303 个字符,预计需要花费 69 分钟才能阅读完成。

作为 Java 开发者,你认为反射这个知识点重要水平,在你心里是什么样的呢?

以前我也只感觉反射十分重要,但总归是听这个文章说,听那个敌人说,学是学了,但却没怎么利用。

当我正式进入到社会当 cv 仔的时候,须要思考的问题多了,缓缓思考问题了,就感觉反射是个力大无穷的货色,更会感觉反射是个无所不能的货色,如各种各样的框架的底层,各种各样的拦截器的实现,反射都是其中少不了的一部分~

如果平时着重于开发业务的话,那么的确会较少应用到反射机制,但并非是说反射它不重要,反射它可是搭建脚手架的根底的根底勒~

文章大抵思路:

全文共 7500 字左右,案例均可运行,浏览工夫大概须要 20 分钟左右,如有问题,请留言或发送邮件(mailto:nzc_wyh@163.com)。

编写的过程中,即便书写完已浏览过,但不免可能会呈现脱漏,如有发现问题请及时分割修改,非常感谢你的浏览,心愿咱们都能成为技术路线上的敌人。

一、反射是什么?

JAVA 反射机制是在运行状态中,对于任意一个类,都可能晓得这个类的所有属性和办法;对于任意一个对象,都可能调用它的任意一个办法和属性;这种 动静获取的信息以及动静调用对象的办法的性能称为 java 语言的反射机制

不过要想解剖一个类,就要先获取到该类的字节码文件对应的 Class 类型的对象.

稍后就会讲到~

反射之所以被称为框架的灵魂“,次要是因为它赋予了咱们在运行时剖析类以及执行类中办法的能力,这种能力也就是咱们常说的动态性,利用这种性质能够使编写的程序更灵便更通用。

反射机制能够用来:

  • 在运行时剖析类的能力,如能够结构任意一个类,能够获取任意一个类的全副信息,
  • 在运行时查看对象,如在运行时判断任意一个对象所属的类
  • 实现泛型数组操作代码,因为在运行时能够获取泛型信息
  • 利用 Method 对象,如咱们常常应用的动静代理,就是应用 Method.invoke() 来实现办法的调用。

反射是一种功能强大且简单的机制,在开发 Java 工具或框架方面,反射更是不可短少的一部分。

二、Class 对象详解

之前说到了,如果要剖析一个类,就必须要获取到该类的字节码文件对应 Class 类型对象。

另外如果有听到 类模板对象,这个说的其实就是Class 对象,大家不要误会了。

2.1、如何获取到 Class 对象呢?

失去 Class 的形式总共有四种:

  • 通过对象调用 getClass()办法来获取
  • 间接通过 类名.class 的形式失去
  • 通过 Class对象的 forName() 静态方法来获取
  • Classloader,通过类加载器进行获取
 /**
  * @description:
  * @author: Ning Zaichun
  * @date: 2022 年 09 月 15 日 22:49
  */
 public class ClassDemo01 {
 ​
     @Test
     public void test1() throws Exception {//1、通过对象调用 getClass() 办法来获取
         //  类型的对象,而我不晓得你具体是什么类,用这种办法
         Student student = new Student();
         Class studentClass1 = student.getClass();
         System.out.println(studentClass1);
         // out: class com.nzc.Student
 ​
         //2、间接通过 ` 类名.class` 的形式失去
         // 任何一个类都有一个隐含的动态成员变量 class
         // Class studentClass2 = Student.class;
         Class<?> studentClass2 = Student.class;
         System.out.println(studentClass2);
         // out: class com.nzc.Student
         System.out.println("studentClass1 和 studentClass2 是否相等 ==>" + (studentClass1 == studentClass2));
         // studentClass1 和 studentClass2 是否相等 ==>true
 ​
         //3、通过 Class 对象的 forName() 静态方法来获取,应用的最多
         //   但须要抛出或捕捉 ClassNotFoundException 异样
         Class<?> studentClass3 = Class.forName("com.nzc.Student");
         System.out.println(studentClass3);
         // out: class com.nzc.Student
         System.out.println("studentClass1 和 studentClass3 是否相等 ==>" + (studentClass1 == studentClass3));
         //studentClass1 和 studentClass3 是否相等 ==>true
 ​
         //4、应用类的加载器:ClassLoader 来获取 Class 对象
         ClassLoader classLoader = ClassDemo01.class.getClassLoader();
         Class studentClass4 = classLoader.loadClass("com.nzc.Student");
         System.out.println(studentClass4);
         System.out.println("studentClass1 和 studentClass4 是否相等 ==>" + (studentClass1 == studentClass4));
         //studentClass1 和 studentClass4 是否相等 ==>true
     }
 }

在这四种形式中,最常应用的是第三种形式,第一种都间接 new 对象啦,齐全没有必要再应用反射了;第二种形式也曾经明确了类的名称,相当于曾经固定下来,失去了一种动静抉择加载类的成果;而第三种形式,只有传入一个字符串,这个字符串能够是本人传入的,也能够是写在配置文件中的。

像在学 JDBC 连贯的时候,大家必定都应用过 Class对象的 forName() 静态方法来获取 Class 对象,再加载数据库连贯对象,但可能那时候只是匆匆而过罢了。

留神 :不晓得大家有没有察看,我把各种形式所获取到的class 对象,都进行了一番比拟,并且后果都为 true,这是因为 一个类在 JVM 中只会有一个 Class 实例,为了解释此点,我把类加载过程也简略的做了一个陈说。

2.2、类的加载过程

当咱们须要应用某个类时,如果该类还未被加载到内存中,则会经验上面的过程对类进行初始化。

即类的加载 —> 链接 —> 初始化三个阶段。

在这里我只着眼于类的加载过程了,想要理解更为具体的,就须要大家去找找材料看看啦~

加载过程

1、在咱们进行编译后(javac.exe 命令),会生成一个或多个字节码文件(就是我的项目中编译完会呈现的 target 目录下的以.class 结尾的文件)

2、接着当咱们应用 java.exe 命令对某个字节码文件进行解释运行时。

3、加载过程

  • 就相当于将 class 文件字节码内容加载到内存中,并将这些静态数据转换成办法区的运行时数据结构;
  • 并生成一个代表这个类的 java.lang.Class 对象,这个加载到内存中的类,咱们称为运行时类,此运行时类,就是 Class的一个实例,所有须要拜访和应用类数据只能通过这个 Class 对象。
  • 所谓 Class 对象,也称为 类模板对象,其实就是 Java 类在 JVM 内存中的一个快照,JVM 将从字节码文件中解析出的常量池、类字段、类办法等信息存储到模板中,这样 JVM 在运行期便能通过类模板而获 取 Java 类中的任意信息,可能对 Java 类的成员变量进行遍历,也能进行 Java 办法的调用。
  • 反射的机制即基于这一根底。如果 JVM 没有将 Java 类的申明信息存储起来,则 JVM 在运行期也无奈进行反射。

4、这个加载的过程还须要类加载器参加,对于类加载器的类型大家能够去理解理解,还有双亲委派机制等,此处我便不再多言

2.3、为了更便于记忆的图

(图片说明:为更好的形容 JVM 中只有一个 Class 对象,而画下此图,心愿通过这张简图,让你记忆更为粗浅)

2.4、Class 罕用的 API

通过 Class 类获取成员变量、成员办法、接口、超类、构造方法等

  • getName():取得类的残缺名字。
  • getFields():取得类的 public 类型的属性。
  • getDeclaredFields():取得类的所有属性。包含 private 申明的和继承类
  • getMethods():取得类的 public 类型的办法。
  • getDeclaredMethods():取得类的所有办法。包含 private 申明的和继承类
  • getMethod(String name, Class[] parameterTypes):取得类的特定办法,name参数指定办法的名字,parameterTypes 参数指定办法的参数类型。
  • getConstructors():取得类的 public 类型的构造方法。
  • getConstructor(Class[] parameterTypes):取得类的特定构造方法,parameterTypes参数指定构造方法的参数类型。
  • newInstance():通过类的不带参数的构造方法创立这个类的一个对象。

另外就还有反射包下的几个罕用的对象 Constructor、Filed、Method 等,别离示意类的结构器、字段属性、办法等等

这些都会在下文缓缓陈说进去~

三、获取运行时类残缺信息并应用

所谓 运行时类,就是程序运行时所创立进去的类,你间接了解为通过反射获取到的类也可。大体意思是如此。

在讲述这一大节时,先要了解 Java 中所有皆对象 这句话。

咱们平时编写一个类,咱们会将它称为一个 Java 对象,然而在反射这里将此概念再次向上形象了。

类的根本信息:构造方法、成员变量,办法,类上的注解,办法注解,成员变量注解等等,这些都是 Java 对象,也是证实了 Java 中所有皆对象 这句话。

其中 Constructor 就示意构造方法的对象,他蕴含了构造方法的所有信息,

Field、Method等等都是如此。

不要太过于麻烦和反复书写,我将案例中操作的所有相干代码,都放在此处了,案例中的依赖全副基于此。

 public interface TestService {}
 @Target({ElementType.TYPE,ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface MarkAnnotation {
 ​
     String value() default "";
 ​
 }
 public class Generic<T> { }
 ​
 public interface GenericInterface<T> { }
 ​
 // TYPE 示意能够标记在类上
 //  PARAMETER 示意能够标记在办法形式参数
 //  METHOD 办法
 //  FIELD 成员属性上
 @Target({ElementType.TYPE,ElementType.METHOD, ElementType.FIELD,ElementType.PARAMETER})
 @Retention(RetentionPolicy.RUNTIME) // 这里表明是运行时注解
 @Documented
 public @interface LikeAnnotation {
 ​
     String value() default "";
 ​
 ​
     String[] params() default {};}
 @Data
 @MarkAnnotation
 public class Person implements TestService{
     private String sex;
 ​
     public Integer age;
 ​
     private void mySex(String sex){System.out.println("我的性别"+sex);
     }
 ​
     public void myAge(Integer age){System.out.println("我的年龄"+age);
     }
 ​
 ​
 }
 @ToString(callSuper = true) // 减少这行是为了打印时将父类属性也打印进去,不便查看~
 @Data
 @LikeAnnotation(value = "123")
 public class Student extends Person implements Serializable {
     @LikeAnnotation
     private String username;
 ​
     @LikeAnnotation
     public String school;
 ​
     private Double score;
 ​
     public Student() {}
 ​
     private Student(String username) {this.username = username;}
 ​
     public Student(String username, String school) {
         this.username = username;
         this.school = school;
     }
 ​
     @LikeAnnotation
     public void hello() {System.out.println("世界,你好");
     }
 ​
     public void say(String username) {System.out.println("你好,我叫" + username);
     }
 ​
     private void myScore(Double score) {System.out.println("我的分数是一个私密货色," + score);
     }
 ​
     public void annotationTest(@LikeAnnotation  String username,@MarkAnnotation String str){System.out.println( "测试获取办法参数中的注解信息");
     }
 }

3.1、反射获取运行时类构造方法并应用

class 获取构造方法的相干API

 // 获取所有的构造函数
 Constructor<?>[] getConstructors()
 ​
 // 获取 public 或 private 润饰的狗赞函数,只有参数匹配即可
 Constructor<?>[] getDeclaredConstructors()
 ​
 // 获取所有 public 润饰的 构造函数
 Constructor<T> getConstructor(Class<?>... parameterTypes)
     
 // 调用此办法,创立对应的运行时类的对象。public T newInstance(Object ... initargs)

newInstance():调用此办法,创立对应的运行时类的对象。外部调用了运行时类的空参的结构器。

要想此办法失常的创立运行时类的对象,要求:

  • 运行时类必须提供空参的结构器;
  • 空参的结构器的拜访权限得够。通常,设置为 public。

为什么要 javabean 中要求提供一个 public 的空参结构器?

起因:1、便于通过反射,创立运行时类的对象;2、便于子类继承此运行时类时,默认调用 super() 时,保障父类有此构 造器。

想要更具体的理解,倡议去看生成的字节码文件,在那里可能给出你答案。

测试

 /**
  * @description:
  * @author: Ning Zaichun
  * @date: 2022 年 09 月 17 日 1:17
  */
 public class ConstructorTest {
 ​
     /**
      * 获取私有、公有的构造方法 并调用创建对象
      */
     @Test
     public void test1() throws Exception {Class<?> aClass = Class.forName("com.nzc.Student");
 ​
         System.out.println("====== 获取全副 public 润饰的构造方法 =========");
         Constructor<?>[] constructors = aClass.getConstructors();
         for (Constructor<?> constructor : constructors) {System.out.println(constructor);
         }
         System.out.println("====== 获取 public、private 润饰的构造方法,只有参数匹配即可 =========");
         /**
          * 这里的参数 Class<?>... parameterTypes 填写的是参数的类型,而并非某个精确的值信息
          */
         Constructor<?> constructor = aClass.getDeclaredConstructor(String.class);
         System.out.println(constructor);
         // 因为此构造函数是 private 润饰,如果不设置暴力反射,则没有权限拜访。// 这里 setAccessible(true) 是设置暴力反射,如果不设置,则会报错,constructor.setAccessible(true);
         // 这里调用的有参结构,所以在调用 newInstance 办法时,也须要填写参数
         Object o1 = constructor.newInstance("宁在春");
         constructor.setAccessible(false);
         System.out.println("o1===>"+o1);
 ​
         /**
          * 如果须要获取有参结构,只须要填写对应的参数类型即可,* 获取无参结构,填 null 或不填都可。*/
         Constructor<?> constructor1 = aClass.getConstructor();
         Constructor<?> constructor2 = aClass.getConstructor(String.class,String.class);
         System.out.println("无参结构 ==>"+constructor1);
         System.out.println("有参结构 ==>"+constructor2);
         Object o2 = constructor1.newInstance();
         Object o3 = constructor2.newInstance("宁在春 2","xxxx 社会");
         System.out.println("o2===>"+o2);
         System.out.println("o3===>"+o3);
     }
 }

既然可能获取到构造方法,那么也就是能够应用的,用 Constructor.newInstance() 办法来调用构造方法即可,在下列的打印信息中,也能够看进去的确如此,如果明确要获取为 Student 对象的话,进行强转即可。

 ====== 获取全副 public 润饰的构造方法 =========
 public com.nzc.Student(java.lang.String,java.lang.String)
 public com.nzc.Student()
 ====== 获取 public、private 润饰的构造方法,只有参数匹配即可 =========
 private com.nzc.Student(java.lang.String)
 o1===>Student(username= 宁在春, school=null, age=null)
 无参结构 ==>public com.nzc.Student()
 有参结构 ==>public com.nzc.Student(java.lang.String,java.lang.String)
 o2===>Student(username=null, school=null, age=null)
 o3===>Student(username= 宁在春 2, school=xxxx 社会, age=null)

3.2、反射获取运行时类成员变量信息

class对象中获取类成员信息应用到的 API

 Field[] getFields();
  
 Field[] getDeclaredFields();
 ​
 Field getDeclaredField(String name);
 ​
 public native Class<? super T> getSuperclass();

这里的 Field 类对象,其实就是示意类对象中的成员属性,不过还有多了很多其余在反射时须要用到的属性和 API 罢了~

3.2.1、获取公有私有类成员信息

为了有更好的比照,我先编写了一段不应用反射时的失常操作。

 /**
      * 不应用反射的进行操作
      */
 @Test
 public void test1() {Student student = new Student();
     student.setUsername("username");
     student.setSchool("xxxx 社会");
     student.setScore(100.0);
     // 永远三岁的小伙子 哈哈 🤡
     student.setAge(3);
     student.setSex("男");
 ​
     System.out.println("student 信息 ===>" + student);
     // out:student 信息 ===>Student(super=Person(sex= 男, age=3), username=username, school=xxxx 社会, score=100.0)
 }

当初我再应用反射来实现上述操作~

在实现之前,还是先来看看如何获取私有、公有的成员变量吧

 @Test
 public void test2() throws Exception {Class<?> stuClass = Class.forName("com.nzc.Student");
 ​
     System.out.println("======== 获取所有 public 润饰的成员属性(包含父类属性)=====");
     Field[] fields = stuClass.getFields();
     for (Field field : fields) {System.out.println(field);
     }
     //public java.lang.String com.nzc.Student.school
     //public java.lang.Integer com.nzc.Person.age
 ​
     System.out.println("======== 获取所有 (public、private、protected 等润饰的) 属性成员(不包含父类成员属性)=====");
     Field[] fields1 = stuClass.getDeclaredFields();
     for (Field field : fields1) {System.out.println(field);
     }
     //private java.lang.String com.nzc.Student.username
     //public java.lang.String com.nzc.Student.school
     //private java.lang.Double com.nzc.Student.score
 ​
     System.out.println("======== 通过反射,获取对象指定的属性 =====");
     Field username = stuClass.getDeclaredField("username");
     System.out.println("username===>" + username);
 }

然而你发现没有,这无奈获取到父类的成员变量信息,父类的信息,咱们该如何获取呢?

3.2.2、获取父类成员属性信息

其实一样的,咱们也是要获取到父类的 Class 对象,在 Class API 中有一个 getSuperClass() 办法能够获取到父类的 class 对象,其余的操作都是一样的~

 @Test
 public void test5() throws ClassNotFoundException {Class<?> stuClass = Class.forName("com.nzc.Student");
     System.out.println("======== 获取所有属性成员(包含父类成员属性)=====");
     Class clazz = stuClass;
     List<Field> fieldList = new ArrayList<>();
     while (clazz != null) {fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
         clazz = clazz.getSuperclass();}
     Field[] fields2 = new Field[fieldList.size()];
     fieldList.toArray(fields2);
     for (Field field : fields2) {System.out.println(field);
     }
     //private java.lang.String com.nzc.Student.username
     //public java.lang.String com.nzc.Student.school
     //private java.lang.Double com.nzc.Student.score
     //private java.lang.String com.nzc.Person.sex
     //public java.lang.Integer com.nzc.Person.age
 }

到这里咱们曾经晓得如何获取到类的成员变量信息了,就能够来看看如何给它们设置值,或者通过反射的形式来获取到值了。

3.2.3、设置或批改类成员变量值

 @Test
 public void test3() throws Exception {Class<?> stuClass = Class.forName("com.nzc.Student");
     System.out.println("获取到 filed 后,设置值或批改值");
     Constructor<?> constructor = stuClass.getConstructor();
     Object o = constructor.newInstance();
 ​
     // 操作 pubilc 属性
     Field school = stuClass.getDeclaredField("school");
     school.set(o,"xxxx 社会");
     System.out.println(o);
     //Student(super=Person(sex=null, age=null), username=null, school=xxxx 社会, score=null)
     // 另外既然能够设置,那么天然也是能够获取的
     System.out.println(school.get(o));
     //xxxx 社会
 ​
     // 操作 private 润饰的成员属性
     Field name = stuClass.getDeclaredField("username");
     // setAccessible(true) 因为是获取 private 润饰的成员变量
     // 不设置暴力反射 是无奈获取到的,会间接报 IllegalAccessException 异样
     name.setAccessible(true);
     name.set(o,"Ningzaichun");
     name.setAccessible(false);
     System.out.println(o);
     //Student(super=Person(sex=null, age=null), username=Ningzaichun, school=xxxx 社会, score=null)
 }

其实看到这里对于反射曾经有个大略的印象了,并且这都是比拟平时且实用的一些办法。

3.2.4、获取成员变量注解信息

 @Test
 public void test4() throws Exception {Class<?> stuClass = Class.forName("com.nzc.Student");
     Field school = stuClass.getDeclaredField("school");
 ​
     LikeAnnotation annotation = school.getAnnotation(LikeAnnotation.class);
     System.out.println(annotation);
     //@com.nzc.LikeAnnotation(params=[], value=)
 ​
     Annotation[] annotations = school.getAnnotations();
     for (Annotation annotation1 : annotations) {System.out.println(annotation1);
     }
     //@com.nzc.LikeAnnotation(params=[], value=)
 ​
 ​
     LikeAnnotation declaredAnnotation = school.getDeclaredAnnotation(LikeAnnotation.class);
     System.out.println(declaredAnnotation);
     //@com.nzc.LikeAnnotation(params=[], value=)
 ​
     Annotation[] declaredAnnotations = school.getDeclaredAnnotations();
     for (Annotation declaredAnnotation1 : declaredAnnotations) {System.out.println(declaredAnnotation1);
     }
     //@com.nzc.LikeAnnotation(params=[], value=)
 }

对于 getAnnotation、getDeclaredAnnotation 的剖析大家能够看看上面这篇文章 @Repeatable 详解 -getAnnotation、getDeclaredAnnotation 获取不到对象

对于想要更具体的理解 Java 注解的敌人,大家能够持续找找相干材料。

3.3、反射获取运行时类对对象办法信息并调用

Class 对象中应用的相干 API

 Method[] getMethods();
 ​
 Method getMethod(String name, Class<?>... parameterTypes);
 ​
 Method[] getDeclaredMethods();
 ​
 Method getDeclaredMethod(String name, Class<?>... parameterTypes);
 ​
 // 获取办法返回值类型
 Class<?> getReturnType();
 ​
 // obj – 调用底层办法的对象 
 // args – 用于办法调用的参数 
 // return 应用参数 args 在 obj 上调度此对象示意的办法的后果
 Object invoke(Object obj, Object... args)

3.3.1、获取对象办法

获取私有、公有办法

 @Test
 public void test1() throws Exception {Class<?> stuClass = Class.forName("com.nzc.Student");
 ​
     System.out.println("======== 获取所有 public 润饰的办法 ======");
     Method[] methods = stuClass.getMethods();
     for (Method method : methods) {System.out.println(method);
     }
     //public boolean com.nzc.Student.equals(java.lang.Object)
     //public java.lang.String com.nzc.Student.toString()
     //public int com.nzc.Student.hashCode()
     //public void com.nzc.Student.hello()
     //public void com.nzc.Student.say(java.lang.String)
     //public java.lang.Double com.nzc.Student.getScore()
     //public void com.nzc.Student.setScore(java.lang.Double)
     //public void com.nzc.Student.setSchool(java.lang.String) .... 还有一些没写进去了
 ​
     System.out.println("======== 获取对象【指定的 public 润饰的办法】======");
     // 第一个参数为办法名,此处是获取 public 润饰的无参办法
     Method hello = stuClass.getMethod("hello");
     System.out.println("hello===>"+hello);
     // 带参办法,第二个参数填写【参数类型】Method say = stuClass.getMethod("say", String.class);
     System.out.println("say===>"+say);
     //hello===>public void com.nzc.Student.hello()
     //say===>public void com.nzc.Student.say(java.lang.String)
 ​
 ​
     // 参数为 办法名,此处是获取 private 润饰的无参办法
     // stuClass.getDeclaredMethod("");
     Method myScore = stuClass.getDeclaredMethod("myScore", Double.class);
     System.out.println("myScore==>"+myScore);
     //myScore==>private void com.nzc.Student.myScore(java.lang.Double)
 ​
     System.out.println("======= 获取所有的办法 =======");
     Method[] declaredMethods = stuClass.getDeclaredMethods();
     for (Method declaredMethod : declaredMethods) {System.out.println(declaredMethod);
     }
     //public void com.nzc.Student.say(java.lang.String)
     //private void com.nzc.Student.myScore(java.lang.Double)
     //public java.lang.Double com.nzc.Student.getScore()
     //public void com.nzc.Student.setScore(java.lang.Double)
     //protected boolean com.nzc.Student.canEqual(java.lang.Object)
 }

既然能获取到,那么也是可能调用的啦~,

3.3.2、调用对象办法

还记得常常能看到的一个 invoke() 办法吗,这里就是调用 method.invoke() 办法来实现办法的调用。

 @Test
 public void test2() throws Exception {Class<?> stuClass = Class.forName("com.nzc.Student");
     Constructor<?> constructor = stuClass.getConstructor();
     Object o = constructor.newInstance();
 ​
     Method myScore = stuClass.getDeclaredMethod("myScore", Double.class);
     myScore.setAccessible(true);
     // 调用办法
     myScore.invoke(o, 99.0);
     // 相当于 Student student= new Student();
     // student.myScore(99.0);
     myScore.setAccessible(false);
 }

之前论述了 Method它自身就记录了办法的所有信息,咱们实现调用也就是第一步罢了,说它能够获取到过后定义方法的所有都不为过,接下来一步一步来说吧。

3.3.3、获取办法上的注解信息

 @Test
 public void test2() throws Exception {Class<?> stuClass = Class.forName("com.nzc.Student");
     System.out.println("==== 获取成员变量上指定的注解信息 ===");
 ​
     Field username = stuClass.getDeclaredField("username");
     System.out.println(username);
     //private java.lang.String com.nzc.Student.username
     Annotation annotation = username.getAnnotation(LikeAnnotation.class);
     System.out.println(annotation);
     //@com.nzc.LikeAnnotation(params=[], value=)
 ​
     Method hello = stuClass.getDeclaredMethod("hello");
     LikeAnnotation annotation1 = hello.getAnnotation(LikeAnnotation.class);
     System.out.println(hello+"===="+annotation1);
     // public void com.nzc.Student.hello()====@com.nzc.LikeAnnotation(params=[], value=)
 }

3.3.4、获取办法参数及参数注解信息

不过在写我的项目时,有可能还会要获取办法参数上的注解,那该如何获取办法参数呢?又该如何获取办法参数的注解信息呢?

 @Test
 public void test3() throws Exception {Class<?> stuClass = Class.forName("com.nzc.Student");
     System.out.println("==== 获取办法参数中的注解信息 ===");
 ​
     Method annotationTest = stuClass.getDeclaredMethod("annotationTest",String.class,String.class);
 ​
     // 获取办法的返回值类型
     Class<?> returnType = annotationTest.getReturnType();
     // 获取权限修饰符
         System.out.println(Modifier.toString(annotationTest.getModifiers()) );
     // 获取到全副的办法参数
     Parameter[] parameters = annotationTest.getParameters();
     for (Parameter parameter : parameters) {Annotation annotation = parameter.getAnnotation(LikeAnnotation.class);
         if(annotation!=null){
             // 参数类型
             Class<?> type = parameter.getType();
             // 参数名称
             String name = parameter.getName();
             System.out.println("参数类型"+type+"参数名称 ==>"+name+"参数上的注解信息"+annotation);
             // 参数类型 class java.lang.String  参数名称 ==>arg0 参数上的注解信息 @com.nzc.LikeAnnotation(params=[], value=)
         }
     }
     // 获取参数上全副的注解信息
     Annotation[][] parameterAnnotations = annotationTest.getParameterAnnotations();
     for (int i = 0; i < parameterAnnotations.length; i++) {for (int i1 = 0; i1 < parameterAnnotations[i].length; i1++) {System.out.println(parameterAnnotations[i][i1]);
         }
     }
     // @com.nzc.LikeAnnotation(params=[], value=)
     //@com.nzc.MarkAnnotation(value=)
     int parameterCount = annotationTest.getParameterCount();
     System.out.println("获取参数个数 ==>"+parameterCount);
 }
 ​
 ​

3.3.5、获取办法返回参数和办法权限修饰符

 @Test
 public void test33() throws Exception {Class<?> stuClass = Class.forName("com.nzc.Student");
 ​
     Method annotationTest = stuClass.getDeclaredMethod("annotationTest",String.class,String.class);
     // 获取办法的返回值类型
     Class<?> returnType = annotationTest.getReturnType();
     System.out.println(returnType);
     //void
     // 获取权限修饰符
     System.out.println(Modifier.toString(annotationTest.getModifiers()) );
     //public
 }

3.4、反射获取运行时类信息、接口信息、包信息

3.4.1、获取运行时类的接口信息

 /**
      *  获取运行时类实现的接口
      */
 @Test
 public void test5() {
     Class clazz = Student.class;
     Class[] interfaces = clazz.getInterfaces();
     for (Class c : interfaces) {System.out.println(c);
     }
     System.out.println("====================");
     // 获取运行时类的父类实现的接口
     Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
     for (Class c : interfaces1) {System.out.println(c);
     }
 }

3.4.2、获取类所在包的信息

 /**
      * 获取运行时类所在的包
      */
 @Test
 public void test6() {
     Class clazz = Person.class;
     Package pack = clazz.getPackage();
     System.out.println(pack);
     // out:package com.nzc
 }

3.4.3、获取类上注解信息

 @Test
 public void test1() throws Exception {Class<?> stuClass = Class.forName("com.nzc.Student");
     System.out.println("==== 获取类上指定的注解信息 ===");
 ​
     LikeAnnotation annotation = stuClass.getAnnotation(LikeAnnotation.class);
     System.out.println(annotation);
     //@com.nzc.LikeAnnotation(params=[], value=123)
     System.out.println("获取注解上的值信息 ==>"+annotation.value());
     // 获取注解上的值信息 ==>123
     //annotation.params(); 注解有几个属性,就能够获取几个属性
 ​
     System.out.println("====  获取类上全副注解信息 ===");
 ​
     Annotation[] annotations = stuClass.getAnnotations();
     for (Annotation annotation1 : annotations) {System.out.println(annotation1);
         //@com.nzc.LikeAnnotation(params=[], value=123)
     }
 ​
     LikeAnnotation declaredAnnotation = stuClass.getDeclaredAnnotation(LikeAnnotation.class);
     System.out.println("declaredAnnotation==>"+declaredAnnotation);
     //declaredAnnotation==>@com.nzc.LikeAnnotation(params=[], value=123)
     Annotation[] declaredAnnotations = stuClass.getDeclaredAnnotations();
 ​
 }

留神 :lombok 相干的注解是无奈获取到的,因为 lombok 注解为编译时注解,并非是运行时注解,在编译实现后,lombok 注解并不会保留于class 文件中,因而是无奈通过反射获取到的。

@Data 也表明了它的存在级别为源码级别,而运行时存在注解 @Retention@Retention(RetentionPolicy.RUNTIME)

3.5、反射获取运行时类的父类的泛型信息、接口泛型信息

 @Test
 public void test8(){
     Class clazz = Person.class;
 ​
     // 获取泛型父类信息
     Type genericSuperclass = clazz.getGenericSuperclass();
     System.out.println(genericSuperclass);
     // com.nzc.Generic<java.lang.String>
     // 获取泛型接口信息
     Type[] genericInterfaces = clazz.getGenericInterfaces();
 ​
     for (Type genericInterface : genericInterfaces) {System.out.println(genericInterface);
         //interface com.nzc.TestService
         //com.nzc.GenericInterface<java.lang.String>
     }
 }

四、反射利用场景及实战案例

5.1、那到底什么时候会应用反射

一句话说它的利用场景就是:确定不下来到底应用哪个类的时候,比方你要开发一个通用工具类,为了达到通用性,传入的参数对象,个别都是无奈限度的,这个时候就是要用到反射啦~。

反射的特色:动态性

5.2、AOP + 自定义注解 批改前端传递的参数信息

需要如下:我当初引入了一个第三方 jar 包,外面有一个 MyBatis-Plus 查问结构器,其中结构 LIKE条件查问的条件是以后端传过来的参数带有逗号时,拼接为 LIKE 查问条件。

要害代码:

标识在对象的某个成员属性上

 @Target({ElementType.METHOD, ElementType.FIELD})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface LikeAnnotation {
 ​
     String value() default "";}
 ​

标识在 Controller 层上,以此来判断那些申请是须要被切入的。

 @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface MarkAnnotation {
 ​
     String value() default "";
 ​
 }

一个非常简单的 Javabean

 @Data
 public class Login {
 ​
     @LikeAnnotation(value = "username")
     private String username;
 ​
     private String password;
 ​
     public Login() {}
 ​
     public Login(String username, String password) {
         this.username = username;
         this.password = password;
     }
 }
 ​

Controller 类

 @Slf4j
 @RestController
 public class HelloController {
 ​
     @MarkAnnotation
     @GetMapping("/search")
     public Login getLikeLogin(Login login){System.out.println(login);
         return login;
     }
 ​
 }

重点重点,切面类 LikeAnnotationAspect,解决逻辑全副在此处

 @Aspect
 @Component
 @Slf4j
 public class LikeAnnotationAspect {
 ​
     // 标记了 @MarkAnnotation 注解的才切入 升高性能耗费
     @Pointcut("@annotation(com.nzc.annotation.MarkAnnotation)")
     public void pointCut() {​}
 ​
     // 获取以后类及父类所有的成员属性
     private static Field[] getAllFields(Object object) {Class<?> clazz = object.getClass();
         List<Field> fieldList = new ArrayList<>();
         while (clazz != null) {fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
             clazz = clazz.getSuperclass();}
         Field[] fields = new Field[fieldList.size()];
         fieldList.toArray(fields);
         return fields;
     }
 ​
     @Around("pointCut()")
     public Object doAround(ProceedingJoinPoint pjp) throws Throwable {long start = System.currentTimeMillis();
         Object[] args = pjp.getArgs();
         // 获取到第一个参数
         Object parameter = args[0];
 ​
         log.info("parameter==>{}", parameter);
         if (parameter == null) {return pjp.proceed();
         }
 ​
         Field[] fields = getAllFields(parameter);
 ​
         for (Field field : fields) {log.debug("------field.name------" + field.getName());
             if (field.getAnnotation(LikeAnnotation.class) != null) {
                 try {field.setAccessible(true);
                     Object username = field.get(parameter);
                     field.setAccessible(false);
                     if (username != null && !username.equals("")) {field.setAccessible(true);
                         field.set(parameter, "," + username + ",");
                         field.setAccessible(false);
                     }
                 } catch (Exception e) {}}
         }
         // 调用办法
         Object result = pjp.proceed();
         long end = System.currentTimeMillis();
         log.debug("批改耗时 ==>" + (end - start) + "ms");
         return result;
     }
 ​
 }

实现成果:

5.3、Mybatis 拦截器实现主动填充创建人、批改人信息

看似如同写业务的咱们,没有怎么接触 Java 反射,但实际上可能处处都隐含着反射。

应用过 Mybatis-Plus 的敌人,应该晓得,能够设置主动填充数据(创立工夫、更新工夫、创建人、更新人等),不过那个是实现 MetaObjectHandler 接口进行解决的。

然而明天的话,我用 Mybatis 原生的拦截器来进行一番实现,实现每次更新、增加时主动填充创建人、更新人等,表里没工夫字段,就没演示工夫了,但实现原理都统一。

 import lombok.extern.slf4j.Slf4j;
 import org.apache.ibatis.binding.MapperMethod.ParamMap;
 import org.apache.ibatis.executor.Executor;
 import org.apache.ibatis.mapping.MappedStatement;
 import org.apache.ibatis.mapping.SqlCommandType;
 import org.apache.ibatis.plugin.*;
 import org.springframework.stereotype.Component;
 ​
 import java.lang.reflect.Field;
 import java.util.*;
 ​
 /**
  * mybatis 拦截器,主动注入创建人、创立工夫、批改人、批改工夫
  * @author Ning Zaichun
  */
 @Slf4j
 @Component
 @Intercepts({@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class}) })
 public class MybatisInterceptor implements Interceptor {public static Field[] getAllFields(Object object) {Class<?> clazz = object.getClass();
         List<Field> fieldList = new ArrayList<>();
         while (clazz != null) {fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
             clazz = clazz.getSuperclass();}
         Field[] fields = new Field[fieldList.size()];
         fieldList.toArray(fields);
         return fields;
     }
 ​
     @Override
     public Object intercept(Invocation invocation) throws Throwable {MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
         String sqlId = mappedStatement.getId();
         log.info("------sqlId------" + sqlId);
 //2022-09-17 17:16:50.714  INFO 14592 --- [nio-8080-exec-1] com.nzc.tree.commons.MybatisInterceptor  : ------sqlId------com.nzc.tree.mapper.CategoryMapper.updateById
         SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
         Object parameter = invocation.getArgs()[1];
         log.info("------sqlCommandType------" + sqlCommandType);
 //2022-09-17 17:16:50.714  INFO 14592 --- [nio-8080-exec-1] com.nzc.tree.commons.MybatisInterceptor  : ------sqlCommandType------UPDATE
 ​
         if (parameter == null) {return invocation.proceed();
         }
         if (SqlCommandType.INSERT == sqlCommandType) {Field[] fields = getAllFields(parameter);
             for (Field field : fields) {log.info("------field.name------" + field.getName());
                 try {if ("createBy".equals(field.getName())) {field.setAccessible(true);
                         Object local_createBy = field.get(parameter);
                         field.setAccessible(false);
                         if (local_createBy == null || local_createBy.equals("")) {field.setAccessible(true);
                                 field.set(parameter, "nzc-create");
                                 field.setAccessible(false);
                             }
                     }
                 } catch (Exception e) {e.printStackTrace();
                 }
             }
         }
         if (SqlCommandType.UPDATE == sqlCommandType) {Field[] fields = null;
             if (parameter instanceof ParamMap) {ParamMap<?> p = (ParamMap<?>) parameter;
                 if (p.containsKey("et")) {parameter = p.get("et");
                 } else {parameter = p.get("param1");
                 }
                 if (parameter == null) {return invocation.proceed();
                 }
 ​
                 fields = getAllFields(parameter);
             } else {fields = getAllFields(parameter);
             }
 ​
             for (Field field : fields) {log.info("------field.name------" + field.getName());
                 try {if ("updateBy".equals(field.getName())) {field.setAccessible(true);
                             field.set(parameter,"nzc-update");
                             field.setAccessible(false);
                     }
                 } catch (Exception e) {e.printStackTrace();
                 }
             }
         }
         return invocation.proceed();}
 ​
     @Override
     public Object plugin(Object target) {return Plugin.wrap(target, this);
     }
 ​
     @Override
     public void setProperties(Properties properties) {// TODO Auto-generated method stub}
 }

外面牵扯到的一些 Mybatis 中的一些对象,我没细说了,大家打印进去的时候都能够看到的。

测试:

执行的 SQL 语句打印信息

后果:

反射的个性,看似和咱们天天写业务没啥关系,然而它其实始终随同着咱们,这也是 Java 开发者的基础知识,根底不牢,地动山摇~

五、反射的优缺点

长处 :反射进步了程序的灵活性和扩展性,升高耦合性,进步自适应能力。它容许程序创立和管制任何类的对象,无需提前硬编码指标类; 对于任意一个类,都可能晓得这个类的所有属性和办法;对于任意一个对象,都可能调用它的任意一个办法

毛病:让咱们在运行时有了剖析操作类的能力,这同样也减少了平安问题。比方能够忽视泛型参数的安全检查(泛型参数的安全检查产生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说理论是影响不大的。

反射是否真的会让你的程序性能升高吗?

1. 反射大略比间接调用慢 50~100 倍,然而须要你在执行 100 万遍的时候才会有所感觉

2. 判断一个函数的性能,你须要把这个函数执行 100 万遍甚至 1000 万遍

3. 如果你只是偶然调用一下反射,请遗记反射带来的性能影响

4. 如果你须要大量调用反射,请思考缓存。

5. 你的编程的思维才是限度你程序性能的最次要的因素

小结

仔细阅读下来你会发现,

正如文中所说,所谓 Class 对象,也称为 类模板对象,其实就是 Java 类在 JVM 内存中的一个快照,JVM 将从字节码文件中解析出的常量池、类字段、类办法等信息存储到模板中,这样 JVM 在运行期便能通过类模板而获取 Java 类中的任意信息,可能对 Java 类的成员变量进行遍历,也能进行 Java 办法的调用,获取到类的任意信息。

反射也就是这样啦,不晓得你会应用啦吗,如果你还没有的话,我感觉能够再读上一遍,顺带本人验证一遍,心愿你能有所播种。

后记

其实想写这篇文章工夫曾经不短了,但 偶然发生(我又常常偶然),所以总是一拖再拖,趁着这个周六,终于把它实现了,我也不晓得你会不会读到此处,看到我发的牢骚。

只是非常简单的心愿每一位阅读者可能有所播种,这应该是我继续写文的高兴吧~

明天又是好值的一天啊~

各位下次见!

写于 2022 年 9 月 17 日,作者:宁在春

本文参加了思否技术征文,欢送正在浏览的你也退出。

正文完
 0