Maison  >  Article  >  Java  >  Quels sont le mécanisme de réflexion Java et les scénarios d'application courants ?

Quels sont le mécanisme de réflexion Java et les scénarios d'application courants ?

王林
王林avant
2023-04-26 21:01:051529parcourir

    1. Qu'est-ce que la réflexion Java ?

    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 类名()方式来获取该类的对象。也就是说我们需要在写代码的时候(编译期或者类加载之前)就知道我们要实例化哪一个类,运行哪一个方法,这种通常被称为静态的类加载。

    但是在有些场景下,我们事先是不知道我们的代码的具体行为的。比如,我们定义一个服务任务工作流,每一个服务任务都是对应的一个类的一个方法。

    Quels sont le mécanisme de réflexion Java et les scénarios dapplication courants ?

    • 服务任务B执行哪一个类的哪一个方法,是由服务任务A的执行结果决定的

    • 服务任务C执行哪一个类的哪一个方法,是由服务任务A和B的执行结果决定的

    • 并且用户不希望服务任务的功能在代码中写死,希望通过配置的方式根据不同的条件执行不同的程序,条件本身也是变化的

    面对这个情况,我们就不能用代码new 类名()来实现了,因为你不知道用户具体要怎么做配置,这一秒他希望服务任务A执行Xxxx类的x方法,下一秒他可能希望执行Yyyy类的y方法。当然你也可以说提需求嘛,用户改一次需求,我改一次代码。这种方式也能需求,但对于用户和程序员个人而言都是痛苦,那么有没有一种方法在运行期动态的改变程序的调用行为的方法呢?这就是要为大家介绍的“java反射机制”。

    那么java的反射机制能够做那些事呢?大概是这样几种:

    • 在程序运行期动态的根据package名.类名

      Mais dans certains scénarios, nous ne connaissons pas à l’avance le comportement spécifique de notre code. Par exemple, si nous définissons un workflow de tâches de service, chaque tâche de service est une méthode d'une classe correspondante.
    • Quel est le mécanisme de réflexion Java et les scénarios d'application courants

    • La méthode de la tâche de service de classe B exécutée est déterminée par les résultats d'exécution de la tâche de service A. La méthode de la tâche de service de classe C exécutée est déterminée par les résultats d'exécution des tâches de service A et B.

    • Et les utilisateurs le font Je ne veux pas que la fonction des tâches de service soit codée en dur dans le code. Ils espèrent exécuter différents programmes selon différentes conditions grâce à la configuration. Les conditions elles-mêmes changent également

    Face à cette situation, nous ne pouvons pas utiliser le Code <. code>new class name () pour y parvenir, car vous ne savez pas comment l'utilisateur le configure spécifiquement. Cette seconde, il souhaite que la tâche de service A exécute la méthode x de la classe Xxxx, et la seconde suivante, il souhaite que la tâche de service A exécute la méthode x de la classe Xxxx. vous souhaiterez peut-être exécuter la méthode y de la classe Yyyy. Bien sûr, vous pouvez également demander des exigences. Une fois que l’utilisateur modifie les exigences, je modifierai le code. Cette méthode peut également être requise, mais elle est pénible pour les utilisateurs et les programmeurs personnellement. Existe-t-il donc un moyen de modifier dynamiquement le comportement d'appel du programme pendant l'exécution ? C'est le "mécanisme de réflexion Java" que je souhaite vous présenter.

    Alors, que peut faire le mécanisme de réflexion de Java ? Ils sont probablement les suivants :

    Instancier dynamiquement des objets de classe basés sur 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

    • Appeler dynamiquement les méthodes de l'objet lors de l'exécution du programme (des méthodes privées peuvent également être appelées)

    • 2. Hello World

      Commençons par commencer, vous pouvez ignorer cette section. Nous définissons une classe appelée Student
    • 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 + "分钟");
          }
      }
    • Si la réflexion n'est pas utilisée, je crois que les amis qui ont appris Java appelleront certainement la méthode du dîner

      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.

    Quels sont le mécanisme de réflexion Java et les scénarios dapplication courants ?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".

      Lorsque Java exécute la compilation, il compile les fichiers Java en fichiers de classe bytecode. Le chargeur de classe charge les fichiers de classe en mémoire pendant la phase de chargement de la classe et instancie un objet java.lang.Class. Par exemple, la classe Student aura les actions suivantes lors de la phase de chargement :
    • Instancier un objet Class dans la mémoire (zone méthode ou zone code) Notez que l'objet Class n'est pas l'objet Student
    • A. La classe Class (fichier de code d'octet) correspond à un objet Class, et il n'y en a qu'un seul

    Quels sont le mécanisme de réflexion Java et les scénarios dapplication courants ?

    Cet objet Class enregistre les informations de base de la classe Student. Par exemple, combien de champs (Filed) possède cette classe Student ? Combien y a-t-il de constructeurs ? Combien de méthodes existe-t-il ? Quelles annotations y a-t-il ? En attente d'informations

    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:在类、成员变量、构造方法、普通方法上都可以加注解

    4.1.获取Class对象的三种方法

    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对象,但是只有第一种可以被称为“反射”。

    4.2.获取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()

    4.3. 获得Class对象的成员变量

    结合上文中的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方法获取所有的成员变量,数组,但是不包含从父类继承而来的成员变量

    4.4.获取Class对象的方法

    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()

    4.5.方法的调用

    实际在上文中已经演示了方法的调用,如下invoke调用dinner方法

    Method dinnerMethod = cls.getDeclaredMethod("dinner");
    dinnerMethod.invoke(obj);  //打印:吃晚餐!

    dinner方法是无参的,那么有参数的方法怎么调用?看看invoke方法定义,第一个参数是Method对象,无论后面Object... args有多少参数就按照方法定义依次传参就可以了。

    public Object invoke(Object obj, Object... args)

    4.6.创建类的对象(实例化对象)

    //获取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

    5.1. 通过配置信息调用类的方法

    将上文的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);
    }

    5.2.结合注解实现特殊功能

    大家如果学习过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了?

    反射和注解结合使用,可以演化出许许多多的应用场景,特别是在框架代码实现方面。等待你去发觉啊!

    5.3.按需加载jar包或class

    在某些场景下,我们可能不希望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!

    Déclaration:
    Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer