图文详解java反射机制及常用应用场景

目录
  • 一、什么是java反射?
  • 二、HelloWorld
  • 三、类加载与反射关系
  • 四、操作反射的java类
    • 4.1.获取Class对象的三种方法
    • 4.2.获取Class类对象的基本信息
    • 4.3.获得Class对象的成员变量
    • 4.4.获取Class对象的方法
    • 4.5.方法的调用
    • 4.6.创建类的对象(实例化对象)
  • 五、反射的常用场景
    • 5.1.通过配置信息调用类的方法
    • 5.2.结合注解实现特殊功能
    • 5.3.按需加载jar包或class
  • 六、反射的优缺点

一、什么是java反射?

在java的面向对象编程过程中,通常我们需要先知道一个Class类,然后new 类名()方式来获取该类的对象。也就是说我们需要在写代码的时候(编译期或者类加载之前)就知道我们要实例化哪一个类,运行哪一个方法,这种通常被称为静态的类加载。

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

  • 服务任务B执行哪一个类的哪一个方法,是由服务任务A的执行结果决定的
  • 服务任务C执行哪一个类的哪一个方法,是由服务任务A和B的执行结果决定的
  • 并且用户不希望服务任务的功能在代码中写死,希望通过配置的方式根据不同的条件执行不同的程序,条件本身也是变化的

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

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

  • 在程序运行期动态的根据package名.类名实例化类对象
  • 在程序运行期动态获取类对象的信息,包括对象的成本变量和方法
  • 在程序运行期动态使用对象的成员变量属性
  • 在程序运行期动态调用对象的方法(私有方法也可以调用)

二、Hello World

先入个门,大佬可以略过这一段。我们定义一个类叫做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 + "分钟");
    }
}

如果不用反射的方式,我相信只要学过java的朋友肯定会调用dinner方法

Student student = new Student();
student.dinner();

如果是反射的方式我们该怎么调用呢?

//获取Student类信息
Class cls = Class.forName("com.zimug.java.reflection.Student");
//对象实例化
Object obj = cls.getDeclaredConstructor().newInstance();
//根据方法名获取并执行方法
Method dinnerMethod = cls.getDeclaredMethod("dinner");
dinnerMethod.invoke(obj);  //打印:吃晚餐!

通过上面的代码我们看到,com.zimug.java.reflection.Student类名和dinner方法名是字符串。既然是字符串我们就可以通过配置文件,或数据库、或什么其他的灵活配置方法来执行这段程序了。这就是反射最基础的使用方式。

三、类加载与反射关系

java的类加载机制还是挺复杂的,我们这里为了不混淆重点,只为大家介绍和“反射”有关系的一部分内容。

java执行编译的时候将java文件编译成字节码class文件,类加载器在类加载阶段将class文件加载到内存,并实例化一个java.lang.Class的对象。比如对于Student类在加载阶段会有如下动作:

  • 在内存(方法区或叫代码区)中实例化一个Class对象,注意是Class对象不是Student对象
  • 一个Class类(字节码文件)对应一个Class对象,并且只有一个
  • 该Class对象保存了Student类的基础信息,比如这个Student类有几个字段(Filed)?有几个构造方法(Constructor)?有几个方法(Method)?有哪些注解(Annotation)?等信息

有了上面的关于Student类的基本信息对象(java.lang.Class对象),在运行期就可以根据这些信息来实例化Student类的对象。

  • 在运行期你可以直接new一个Student对象
  • 也可以使用反射的方法构造一个Student对象

但是无论你new多少个Student对象,不论你反射构建多少个Student对象,保存Student类信息的java.lang.Class对象都只有一个。下面的代码可以证明。

Class cls = Class.forName("com.zimug.java.reflection.Student");
Class cls2 = new Student().getClass();
System.out.println(cls == cls2); //比较Class对象的地址,输出结果是true

四、操作反射的java类

了解了上面的这些基础信息,我们就可以更深入学习反射类相关的类和方法了:

java.lang.Class: 代表一个类

java.lang.reflect.Constructor: 代表类的构造方法

java.lang.reflect.Method: 代表类的普通方法

java.lang.reflect.Field: 代表类的成员变量

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类封装性,类的信息隐藏性和边界被破坏

言尽于此,限于笔者的知识结构,可能有不严谨之处,欢迎大家讨论与指正!期待您的关注,我将持续带来更哇塞的作品,希望大家以后多多支持我们!

(0)

相关推荐

  • JAVA基础之注解与反射的使用方法和场景

    注解 注解定义 Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制. Java 语言中的类.方法.变量.参数和包等都可以被标注.和注释不同,Java 标注可以通过反射获取标注内容.在编译器生成类文件时,标注可以被嵌入到字节码中.Java 虚拟机可以保留标注内容,在运行 时可以获取到标注内容 . 当然它也支持自定义 Java 标注. 注解与注释的区别:注解是给机器看的注释,而注释是给程序员看的提示,编译时自动忽略注释. 使用场景 编译格式检查 反射中解

  • Java反射机制概念、原理与用法总结

    本文实例讲述了Java反射机制概念.原理与用法.分享给大家供大家参考,具体如下: 反射机制是什么 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制. 反射机制能做什么 反射机制主要提供了以下功能: ① 在运行时判断任意一个对象所属的类: ② 在运行时构造任意一个类的对象: ③ 在运行时判断任意一个类所具有的成员变量和方法: ④ 在运行时调用任意一个

  • Java学习之反射机制及应用场景介绍

    前言: 最近公司正在进行业务组件化进程,其中的路由实现用到了Java的反射机制,既然用到了就想着好好学习总结一下,其实无论是之前的EventBus 2.x版本还是Retrofit.早期的View注解框架都或多或少的用到Java的反射机制. 什么是Java反射机制? JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法:这种动态获取的以及动态调用对象的方法的功能称为Java的反射机制. 反射机制提供了哪些功能? 在运行时判定

  • 在实践中了解Java反射机制应用

    引言 Java反射机制是一个非常强大的功能,在很多大型项目比如Spring, Mybatis都可以看见反射的身影.通过反射机制我们可以在运行期间获取对象的类型信息,利用这一特性我们可以实现工厂模式和代理模式等设计模式,同时也可以解决Java泛型擦除等令人苦恼的问题.本文我们就从实际应用的角度出发,来应用一下Java的反射机制. 反射基础 p.s: 本文需要读者对反射机制的API有一定程度的了解,如果之前没有接触过的话,建议先看一下官方文档的Quick Start. 在应用反射机制之前,首先我们先

  • 浅析Java 反射机制的用途和缺点

    反射的用途 Uses of Reflection Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine. This is a relatively advanced feature and should be used only by

  • 图文详解java反射机制及常用应用场景

    目录 一.什么是java反射? 二.HelloWorld 三.类加载与反射关系 四.操作反射的java类 4.1.获取Class对象的三种方法 4.2.获取Class类对象的基本信息 4.3.获得Class对象的成员变量 4.4.获取Class对象的方法 4.5.方法的调用 4.6.创建类的对象(实例化对象) 五.反射的常用场景 5.1.通过配置信息调用类的方法 5.2.结合注解实现特殊功能 5.3.按需加载jar包或class 六.反射的优缺点 一.什么是java反射? 在java的面向对象编

  • 详解JAVA 反射机制

    什么是反射? 反射机制是在程序运行状态中,对于任意一个类,都能够获取这个类的所有属性和方法: 对于任意一个对象,都能够调用它的任意一个方法和属性: 这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制. 反射的作用 1.可以实现简单的反编译,获取类中的属性和方法等基本信息,.class->java 2.通过反射机制获取类的属性.方法等 在使用eclipse时,通过对象引用.的方式,eclipse就会将这个对象中的所有属性和方法展示出来,这个就是利用的反射机制.其实反射应用最多的

  • 图文详解Java的反射机制

    目录 1.什么是反射 2.Hello,java反射 3.java程序运行的三个阶段 4.反射相关类 5.反射的优化 6.Class类分析 7.获取Class对象的六种方式 8.类加载机制 动态加载和静态加载 类加载流程概述 加载阶段 连接阶段 初始化 9.通过反射获取类的结构信息 1.什么是反射 反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息. 加载类后,在堆中就产生了一个class类型的对象,这个对象包含了类的完整结构的信息,通过这个对象得到类的结构.这

  • Java 类型信息详解和反射机制介绍

    RTTI RTTI(RunTime Type Information)运行时类型信息,能够在程序运行时发现和使用类型信息,把我们从只能在编译期知晓类型信息并操作的局限中解脱出来 传统的多态机制正是 RTTI 的基本使用:假设有一个基类 Shape 和它的三个子类 Circle.Square.Triangle,现在要把 Circle.Square.Triangle 对象放入 List<Shape> 中,在运行时,先把放入其中的所有对象都当作 Object 对象来处理,再自动将类型转换为 Shap

  • 图文详解Java中的序列化机制

    目录 概述 对象序列化和反序列化机制 修改默认的序列化机制 使用transient关键字 自定义readObject.writeObject方法 实现Externalizable接口 serialVersionUID的作用 使用序列化clone 概述 java中的序列化可能大家像我一样都停留在实现Serializable接口上,对于它里面的一些核心机制没有深入了解过.直到最近在项目中踩了一个坑,就是序列化对象添加一个字段以后,使用方系统报了反序列化失败,原因是我们双方的序列化对象没有加上seri

  • 详解JAVA类加载机制

    1.一段简单的代码 首先来一段代码,这个是单例模式,可能有的人不知道什么是单例模式,我就简单说一下 单例模式是指一个类有且只有一种对象实例.这里用的是饿汉式,还有懒汉式,双检锁等等.... 写这个是为了给大家看一个现象 class SingleTon{ public static int count1; public static int count2=0; private static SingleTon instance=new SingleTon(); public SingleTon()

  • 详解Java 反射和反射的应用场景

    反射机制介绍 JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制. 获取 Class 对象的两种方式 如果我们动态获取到这些信息,我们需要依靠 Class 对象.Class 类对象将一个类的方法.变量等信息告诉运行的程序.Java 提供了两种方式获取 Class 对象: 1.知道具体类的情况下可以使用: Class alunbarCla

  • 详解Java序列化机制

    概况 在程序中为了能直接以 Java 对象的形式进行保存,然后再重新得到该 Java 对象,这就需要序列化能力.序列化其实可以看成是一种机制,按照一定的格式将 Java 对象的某状态转成介质可接受的形式,以方便存储或传输.其实想想就大致清楚基本流程,序列化时将 Java 对象相关的类信息.属性及属性值等等保存起来,反序列化时再根据这些信息构建出 Java 对象.而过程可能涉及到其他对象的引用,所以这里引用的对象的相关信息也要参与序列化. Java 中进行序列化操作需要实现 Serializabl

  • 详解Java反射创建对象

    一.什么是反射 Java Reflaction in Action中的解释:反射是运行中的程序检查自己和软件运行环境的能力,它可以根据它发现的进行改变.通俗的讲就是反射可以在运行时根据指定的类名获得类的信息 个人理解:就是我们对于创建对象我们除了通过 new关键字创建外,还能通过什么创建呢?private的属属性真的不能获取吗?反射就能做到打破这些所谓的规则反射和new创建对象谁的效率高? new 二.通过类对象调用newInstance()方法,适用于无参构造方法 2.1 类名.class p

  • 详解JAVA类加载机制(推荐)

    JAVA源码编译由三个过程组成: 1.源码编译机制. 2.类加载机制 3.类执行机制 我们这里主要介绍编译和类加载这两种机制. 一.源码编译 代码编译由JAVA源码编译器来完成.主要是将源码编译成字节码文件(class文件).字节码文件格式主要分为两部分:常量池和方法字节码. 二.类加载 类的生命周期是从被加载到虚拟机内存中开始,到卸载出内存结束.过程共有七个阶段,其中到初始化之前的都是属于类加载的部分 加载----验证----准备----解析-----初始化----使用-----卸载 系统可能

随机推荐