Rumah >Java >javaTutorial >Apakah mekanisme refleksi Java dan senario aplikasi biasa?
Dalam proses pengaturcaraan berorientasikan objek di Java, biasanya kita perlu mengetahui kelas Kelas terlebih dahulu, dan kemudian mendapatkan objek kelas ini dengan cara new 类名()
. Maksudnya, kita perlu tahu kelas mana yang ingin kita nyatakan dan kaedah mana yang hendak dijalankan semasa menulis kod (pada masa penyusunan atau sebelum pemuatan kelas Ini biasanya dipanggil pemuatan kelas statik).
Tetapi dalam sesetengah senario, kami tidak mengetahui gelagat khusus kod kami terlebih dahulu. Sebagai contoh, jika kita mentakrifkan aliran kerja tugas perkhidmatan, setiap tugas perkhidmatan ialah kaedah kelas yang sepadan.
Kaedah kelas yang manakah akan dilaksanakan oleh tugas perkhidmatan B ditentukan oleh hasil pelaksanaan tugas perkhidmatan A
Kelas dan kaedah manakah yang akan dilaksanakan oleh tugas perkhidmatan C ditentukan oleh keputusan pelaksanaan tugas perkhidmatan A dan B
Dan pengguna tidak mahu fungsi tugas perkhidmatan berada dalam Hard-coded dalam kod, kami berharap dapat melaksanakan program yang berbeza mengikut keadaan yang berbeza melalui konfigurasi keadaan itu sendiri juga berubah
Menghadapi situasi ini, kami tidak boleh menggunakan. kodnew 类名()
Disedari, kerana anda tidak tahu bagaimana pengguna mengkonfigurasinya secara khusus Detik ini dia mahu tugas perkhidmatan A melaksanakan kaedah x kelas Xxxx, dan detik seterusnya dia mungkin mahu melaksanakan kaedah y bagi. kelas yyyy. Sudah tentu, anda juga boleh meminta keperluan Setelah pengguna menukar keperluan, saya akan menukar kod. Kaedah ini juga boleh diperlukan, tetapi ia menyakitkan untuk pengguna dan pengaturcara secara peribadi Jadi adakah terdapat cara untuk menukar tingkah laku panggilan program secara dinamik semasa masa jalan? Inilah "mekanisme refleksi java" yang ingin saya perkenalkan kepada anda.
Jadi apakah yang boleh dilakukan oleh mekanisme pantulan Java? Mereka mungkin seperti berikut:
Secara dinamik objek kelas berdasarkan package名.类名
semasa menjalankan program
Secara dinamik diperoleh semasa menjalankan daripada program Maklumat tentang objek kelas, termasuk pembolehubah kos objek dan kaedah
Penggunaan dinamik atribut pembolehubah ahli objek semasa menjalankan program
Semasa menjalankan program Secara berkala panggil kaedah objek secara dinamik (kaedah peribadi juga boleh dipanggil)
Mari kita mulakan dahulu, anda boleh melangkau bahagian ini. Kami mentakrifkan kelas yang dipanggil Pelajar
package com.zimug.java.reflection; public class Student { public String nickName; private Integer age; //这里是private public void dinner(){ System.out.println("吃晚餐!"); } private void sleep(int minutes){ //private修饰符 System.out.println("睡" + minutes + "分钟"); } }
Jika refleksi tidak digunakan, saya percaya bahawa rakan-rakan yang telah belajar Java pasti akan memanggil kaedah makan malam
Student student = new Student(); student.dinner();
Jika refleksi digunakan, bagaimana kita harus memanggilnya ?
//获取Student类信息 Class cls = Class.forName("com.zimug.java.reflection.Student"); //对象实例化 Object obj = cls.getDeclaredConstructor().newInstance(); //根据方法名获取并执行方法 Method dinnerMethod = cls.getDeclaredMethod("dinner"); dinnerMethod.invoke(obj); //打印:吃晚餐!
Kita dapat lihat daripada kod di atas bahawa com.zimug.java.reflection.Nama kelas pelajar dan nama kaedah makan malam adalah rentetan. Oleh kerana ia adalah rentetan, kami boleh melaksanakan program ini melalui fail konfigurasi, pangkalan data atau kaedah konfigurasi fleksibel yang lain. Ini adalah cara paling asas untuk menggunakan refleksi.
Mekanisme pemuatan kelas Java agak rumit untuk tidak mengelirukan perkara utama, kami hanya akan memperkenalkan beberapa kandungan yang berkaitan dengan "reflection ".
Apabila java menyusun, ia menyusun fail java ke dalam fail kelas bytecode Pemuat kelas memuatkan fail kelas ke dalam ingatan semasa fasa pemuatan kelas dan menjadikan objek java.lang.Class. Sebagai contoh, kelas Pelajar akan mempunyai tindakan berikut semasa fasa pemuatan:
Semerta objek Kelas dalam memori (kawasan kaedah atau kawasan kod Ambil perhatian bahawa objek Kelas bukan). objek Pelajar
Kelas Kelas (fail kod bait) sepadan dengan objek Kelas, dan terdapat hanya satu
Objek Kelas ini menyimpan maklumat asas kelas Pelajar, Sebagai contoh, berapa banyak medan (Fail) kelas Pelajar ini ada? Berapakah bilangan pembina yang ada? Berapa banyak kaedah yang ada? Anotasi apa yang ada? dan maklumat lain
Dengan objek maklumat asas di atas (objek java.lang.Class) tentang kelas Pelajar, anda boleh menggunakannya semasa Maklumat masa jalan untuk membuat seketika objek kelas Pelajar.
Semasa runtime anda boleh terus mencipta objek Pelajar baharu
Anda juga boleh menggunakan pantulan untuk membina objek Pelajar
Tetapi tidak kira berapa banyak objek Pelajar baharu yang anda buat, tidak kira berapa banyak objek Pelajar yang anda bina melalui refleksi, hanya terdapat satu objek java.lang.Class yang menyelamatkan Pelajar maklumat kelas. Kod berikut boleh membuktikannya.
Class cls = Class.forName("com.zimug.java.reflection.Student"); Class cls2 = new Student().getClass(); System.out.println(cls == cls2); //比较Class对象的地址,输出结果是true
Selepas memahami maklumat asas di atas, kita boleh mengetahui lebih lanjut tentang kelas dan kaedah yang berkaitan dengan kelas refleksi:
java.lang.Class: Mewakili kelas
java.lang.reflect.Constructor: Mewakili kaedah pembina kelas
java.lang.reflect.Method: Mewakili kaedah biasa bagi class
java.lang.reflect.Field: Mewakili pembolehubah ahli kelas
Java.lang.reflect.Modifier: 修饰符,方法的修饰符,成员变量的修饰符。
java.lang.annotation.Annotation:在类、成员变量、构造方法、普通方法上都可以加注解
Class.forName()
方法获取Class对象
/** * Class.forName方法获取Class对象,这也是反射中最常用的获取对象的方法,因为字符串传参增强了配置实现的灵活性 */ Class cls = Class.forName("com.zimug.java.reflection.Student");
类名.class
获取Class对象
/** * `类名.class`的方式获取Class对象 */ Class clz = User.class;
类对象.getClass()
方式获取Class对象
/** * `类对象.getClass()`方式获取Class对象 */ User user = new User(); Class clazz = user.getClass();
虽然有三种方法可以获取某个类的Class对象,但是只有第一种可以被称为“反射”。
Class cls = Class.forName("com.zimug.java.reflection.Student"); //获取类的包名+类名 System.out.println(cls.getName()); //com.zimug.java.reflection.Student //获取类的父类 Class cls = Class.forName("com.zimug.java.reflection.Student"); //这个类型是不是一个注解? System.out.println(cls.isAnnotation()); //false //这个类型是不是一个枚举? System.out.println(cls.isEnum()); //false //这个类型是不是基础数据类型? System.out.println(cls.isPrimitive()); //false
Class类对象信息中几乎包括了所有的你想知道的关于这个类型定义的信息,更多的方法就不一一列举了。还可以通过下面的方法
获取Class类对象代表的类实现了哪些接口: getInterfaces()
获取Class类对象代表的类使用了哪些注解: getAnnotations()
结合上文中的Student类的定义理解下面的代码
Class cls = Class.forName("com.zimug.java.reflection.Student"); Field[] fields = cls.getFields(); for (Field field : fields) { System.out.println(field.getName()); //nickName } fields = cls.getDeclaredFields(); for (Field field : fields) { System.out.println(field.getName()); //nickName 换行 age }
getFields()
方法获取类的非私有的成员变量,数组,包含从父类继承的成员变量
getDeclaredFields
方法获取所有的成员变量,数组,但是不包含从父类继承而来的成员变量
getMethods() : 获取Class对象代表的类的所有的非私有方法,数组,包含从父类继承而来的方法
getDeclaredMethods() : 获取Class对象代表的类定义的所有的方法,数组,但是不包含从父类继承而来的方法
getMethod(methodName): 获取Class对象代表的类的指定方法名的非私有方法
getDeclaredMethod(methodName): 获取Class对象代表的类的指定方法名的方法
Class cls = Class.forName("com.zimug.java.reflection.Student"); Method[] methods = cls.getMethods(); System.out.println("Student对象的非私有方法"); for (Method m : methods) { System.out.print(m.getName() + ","); } System.out.println(" end"); Method[] allMethods = cls.getDeclaredMethods(); System.out.println("Student对象的所有方法"); for (Method m : allMethods) { System.out.print(m.getName() + ","); } System.out.println(" end"); Method dinnerMethod = cls.getMethod("dinner"); System.out.println("dinner方法的参数个数" + dinnerMethod.getParameterCount()); Method sleepMethod = cls.getDeclaredMethod("sleep",int.class); System.out.println("sleep方法的参数个数" + sleepMethod.getParameterCount()); System.out.println("sleep方法的参数对象数组" + Arrays.toString(sleepMethod.getParameters())); System.out.println("sleep方法的参数返回值类型" + sleepMethod.getReturnType());
上面代码的执行结果如下:
Student对象的非私有方法
dinner,wait,wait,wait,equals,toString,hashCode,getClass,notify,notifyAll, end
Student对象的所有方法
dinner,sleep, end
dinner方法的参数个数0
sleep方法的参数个数1
sleep方法的参数对象数组[int arg0]
sleep方法的参数返回值类型void
可以看到getMethods获取的方法中包含Object父类中定义的方法,但是不包含本类中定义的私有方法sleep。另外我们还可以获取方法的参数及返回值信息:
获取参数相关的属性:
获取方法参数个数:getParameterCount()
获取方法参数数组对象:getParameters() ,返回值是java.lang.reflect.Parameter数组
获取返回值相关的属性
获取方法返回值的数据类型:getReturnType()
实际在上文中已经演示了方法的调用,如下invoke调用dinner方法
Method dinnerMethod = cls.getDeclaredMethod("dinner"); dinnerMethod.invoke(obj); //打印:吃晚餐!
dinner方法是无参的,那么有参数的方法怎么调用?看看invoke方法定义,第一个参数是Method对象,无论后面Object... args
有多少参数就按照方法定义依次传参就可以了。
public Object invoke(Object obj, Object... args)
//获取Student类信息 Class cls = Class.forName("com.zimug.java.reflection.Student"); //对象实例化 Student student = (Student)cls.getDeclaredConstructor().newInstance(); //下面的这种方法是已经Deprecated了,不建议使用。但是在比较旧的JDK版本中仍然是唯一的方式。 //Student student = (Student)cls.newInstance();
通过配置信息调用类的方法
结合注解实现特殊功能
按需加载jar包或class
将上文的hello world中的代码封装一下,你知道类名className和方法名methodName是不是就可以调用方法了?至于你将className和 methodName配置到文件,还是nacos,还是数据库,自己决定吧!
public void invokeClassMethod(String className,String methodName) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //获取类信息 Class cls = Class.forName(className); //对象实例化 Object obj = cls.getDeclaredConstructor().newInstance(); //根据方法名获取并执行方法 Method dinnerMethod = cls.getDeclaredMethod(methodName); dinnerMethod.invoke(obj); }
大家如果学习过mybatis plus都应该学习过这样的一个注解TableName,这个注解表示当前的实体类Student对应的数据库中的哪一张表。如下问代码所示,Student所示该类对应的是t_student这张表。
@TableName("t_student") public class Student { public String nickName; private Integer age; }
下面我们自定义TableName这个注解
@Target(ElementType.TYPE) //表示TableName可作用于类、接口或enum Class, 或interface @Retention(RetentionPolicy.RUNTIME) //表示运行时由JVM加载 public @interface TableName { String value() ; //则使用@TableName注解的时候: @TableName(”t_student”); }
有了这个注解,我们就可以扫描某个路径下的java文件,至于类注解的扫描我们就不用自己开发了,引入下面的maven坐标就可以
<dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.9.10</version> </dependency>
看下面代码:先扫描包,从包中获取标注了TableName注解的类,再对该类打印注解value信息
// 要扫描的包 String packageName = "com.zimug.java.reflection"; Reflections f = new Reflections(packageName); // 获取扫描到的标记注解的集合 Set<Class<?>> set = f.getTypesAnnotatedWith(TableName.class); for (Class<?> c : set) { // 循环获取标记的注解 TableName annotation = c.getAnnotation(TableName.class); // 打印注解中的内容 System.out.println(c.getName() + "类,TableName注解value=" + annotation.value());
输出结果是:
com.zimug.java.reflection.Student类,TableName注解value=t_student
有的朋友会问这有什么用?这有大用处了。有了类定义与数据库表的对应关系,你还能通过反射获取类的成员变量,之后你是不是就可以根据表明t_student和字段名nickName,age构建增删改查的SQL了?全都构建完毕,是不是就是一个基础得Mybatis plus了?
反射和注解结合使用,可以演化出许许多多的应用场景,特别是在框架代码实现方面。等待你去发觉啊!
在某些场景下,我们可能不希望JVM的加载器一次性的把所有classpath下的jar包装载到JVM虚拟机中,因为这样会影响项目的启动和初始化效率,并且占用较多的内存。我们希望按需加载,需要用到哪些jar,按照程序动态运行的需求取加载这些jar。
把jar包放在classpath外面,指定加载路径,实现动态加载。
//按路径加载jar包 File file = new File("D:/com/zimug/commons-lang3.jar"); URL url = file.toURI().toURL(); //创建类加载器 ClassLoader classLoader = new URLClassLoader(new URL[]{url}); Class cls = classLoader.loadClass("org.apache.commons.lang3.StringUtils");
同样的把.class文件放在一个路径下,我们也是可以动态加载到的
//java的.class文件所在路径 File file = new File("D:/com/zimug"); URL url = file.toURI().toURL(); //创建类加载器 ClassLoader classLoader = new URLClassLoader(new URL[]{url}); //加载指定类,package全路径 Class<?> cls = classLoader.loadClass("com.zimug.java.reflection.Student");
类的动态加载能不能让你想到些什么?是不是可以实现代码修改,不需要重新启动web容器?对的,就是这个原理,因为一个类的Class对象只有一个,所以不管你重新加载多少次,都是使用最后一次加载的class对象(上文讲过哦)。
优点:自由,使用灵活,不受类的访问权限限制。可以根据指定类名、方法名来实现方法调用,非常适合实现业务的灵活配置。在框架开发方面也有非常广泛的应用,特别是结合注解的使用。
缺点:
也正因为反射不受类的访问权限限制,其安全性低,很大部分的java安全问题都是反射导致的。
相对于正常的对象的访问调用,反射因为存在类和方法的实例化过程,性能也相对较低
破坏java类封装性,类的信息隐藏性和边界被破坏
Atas ialah kandungan terperinci Apakah mekanisme refleksi Java dan senario aplikasi biasa?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!