Maison >Java >javaDidacticiel >Quels sont le mécanisme de réflexion Java et les scénarios d'application courants ?
Dans le processus de programmation orientée objet de Java, nous devons généralement d'abord connaître une classe Class, puis utiliser new class name()
pour obtenir l'objet de cette classe. C'est-à-dire que nous devons savoir quelle classe nous voulons instancier et quelle méthode exécuter lors de l'écriture du code (au moment de la compilation ou avant le chargement de la classe). C'est ce qu'on appelle généralement le chargement de classe statique. new 类名()
方式来获取该类的对象。也就是说我们需要在写代码的时候(编译期或者类加载之前)就知道我们要实例化哪一个类,运行哪一个方法,这种通常被称为静态的类加载。
但是在有些场景下,我们事先是不知道我们的代码的具体行为的。比如,我们定义一个服务任务工作流,每一个服务任务都是对应的一个类的一个方法。
服务任务B执行哪一个类的哪一个方法,是由服务任务A的执行结果决定的
服务任务C执行哪一个类的哪一个方法,是由服务任务A和B的执行结果决定的
并且用户不希望服务任务的功能在代码中写死,希望通过配置的方式根据不同的条件执行不同的程序,条件本身也是变化的
面对这个情况,我们就不能用代码new 类名()
来实现了,因为你不知道用户具体要怎么做配置,这一秒他希望服务任务A执行Xxxx类的x方法,下一秒他可能希望执行Yyyy类的y方法。当然你也可以说提需求嘛,用户改一次需求,我改一次代码。这种方式也能需求,但对于用户和程序员个人而言都是痛苦,那么有没有一种方法在运行期动态的改变程序的调用行为的方法呢?这就是要为大家介绍的“java反射机制”。
那么java的反射机制能够做那些事呢?大概是这样几种:
在程序运行期动态的根据package名.类名
nom du package.nom de classe
pendant l'exécution du programme
Obtenir dynamiquement des informations sur les objets de classe, y compris les objets, pendant l'exécution du programme. Les variables de coût et méthodes
Utiliser dynamiquement les attributs des variables membres de l'objet lors de l'exécution du programme
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 + "分钟"); } }
Student student = new Student(); student.dinner();Si c'est de la réflexion, comment devrions-nous l'appeler ?
//获取Student类信息 Class cls = Class.forName("com.zimug.java.reflection.Student"); //对象实例化 Object obj = cls.getDeclaredConstructor().newInstance(); //根据方法名获取并执行方法 Method dinnerMethod = cls.getDeclaredMethod("dinner"); dinnerMethod.invoke(obj); //打印:吃晚餐!Grâce au code ci-dessus, nous pouvons voir que le nom de la classe com.zimug.java.reflection.Student et le nom de la méthode Dinner sont des chaînes. Puisqu'il s'agit d'une chaîne, nous pouvons exécuter ce programme via un fichier de configuration, une base de données ou une autre méthode de configuration flexible. C’est la manière la plus basique d’utiliser la réflexion.
3. La relation entre le chargement de classe et la réflexion
Le mécanisme de chargement de classe de Java est encore assez compliqué Afin de ne pas confondre les points clés, nous n'introduireons qu'une partie du contenu lié à la "réflexion".
Avec l'objet d'informations de base ci-dessus (objet java.lang.Class) sur la classe Student, l'objet de classe Student peut être instancié en fonction de ces informations pendant l'exécution. Pendant l'exécution, vous pouvez créer directement un objet Étudiant Vous pouvez également utiliser la réflexion pour construire un objet Étudiant🎜🎜🎜🎜🎜🎜🎜Mais peu importe le nombre d'objets Étudiant que vous créez, peu importe le nombre que vous construction par réflexion Il n'existe qu'un seul objet Student et un objet java.lang.Class qui enregistrent les informations sur la classe Student. Le code suivant peut le prouver. 🎜Class cls = Class.forName("com.zimug.java.reflection.Student"); Class cls2 = new Student().getClass(); System.out.println(cls == cls2); //比较Class对象的地址,输出结果是true🎜4. Fonctionnement des classes Java de réflexion🎜🎜Comprenant les informations de base ci-dessus, nous pouvons en apprendre davantage sur les classes et les méthodes liées aux classes de réflexion : 🎜🎜🎜java.lang.Class : représente une classe🎜🎜java .lang.reflect .Constructor : Représente la méthode constructeur de la classe🎜🎜java.lang.reflect.Method : Représente la méthode ordinaire de la classe🎜🎜java.lang.reflect.Field : Représente les variables membres de la classe🎜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类封装性,类的信息隐藏性和边界被破坏
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!