讲Reflection?就是Java反射了


分享一篇 【Java注解+Java反射+Java类加载机制+javaweb 技术搭建MVC框架并用于项目】,更加深入的学习和配置使用所学知识点。


一、前言

参看:

  1. https://juejin.cn/post/6917050648563777544

  2. https://juejin.cn/post/6909692344291819533

  3. https://blog.csdn.net/a745233700/article/details/82893076

学习反射之前,先回顾一下Java中创建对象的方式

  1. new关键字:最常用最简单的创建对象的方式。
  2. clone()方法:调用对象的clone()方法,JVM会给我们创建一个新的对象,而clone()也分为深克隆和浅克隆
  3. Java反射创建对象:通过Class.forName("path")获取到类对象Class<T>,通过class能获取到获取到实体类对象,这当中会涉及的 类型擦除。
  4. 反序列化:通过序列化技术,从本地文件中可加载一个之前序列化存储的类对象(通过二进制文本转化为具体的Java类)。

反射是框架设计的灵魂,使用反射总能实现出一些骚操作。先看一个例子解一下。

反射实现示例

Hello类

public class Hello {
    // 私有数据
    private String data1;
    // 公有数据
    public String data2;
    // 公有方法
    public String hello(String name, Date date) {
        System.out.println("hello " + name + ",现在是" + date);
        return "hello被调用";
    }
}

使用反射技术调取Hello类的hello方法

public class Reflect {
    public static void main(String[] args) throws Exception {
        // 1.得到Hello的字节码文件
        Class<Hello> cla = Hello.class;
        // 2.获取Hello的构造方法
        Constructor<Hello> helloCon = cla.getConstructor();
        // 3.获取到Hello的实体类
        Hello h = helloCon.newInstance();
        // 4.获取 通过Hello字节码绑定指定的Method
        Method m = cla.getMethod("hello", String.class, Date.class);
        // 5.执行 Hello 的hello()方法
        m.invoke(h,"张三",new Date());
    }
}

结果:

hello 张三,现在是Fri Sep 17 17:15:03 CST 2021

成功调用到Hello类的hello()方法。


二、反射的原理

在上面的示例中,已经感受到了反射带来的作用。那么要给他具体的定义:

Java反射机制的核心是 在程序运行时动态加载类并获取类的详细信息(事先是不知道需要动态加载的类指向的是谁)从而操作类或对象的属性或方法。本质是Java把代码编译成class文件后,JVM获取class对象后,再通过class对象进行反编译,从而获取到某一类对象的各种信息。

反射可实现反编译,将.class文件 --> .java文件。通过反射机制访问java对象的属性,方法,构造方法等。

以为三个点来分析反射的原理:

  1. 类正常加载过程
  2. class对象
  3. 反射

在这里插入图片描述

Class对象的由来是将.class文件读入JVM内存中,并为之创建一个Class对象。

而针对于前面提到的 反射实现示例 中,并没有使用new,而是直接请求获取Hello类的字节码文件,那么此时JVM也会去内存中寻找Hello.class,若在内存中没有找到的话,就会去加载Hello.class到JVM内存。

下面使用一个小小的操作来帮助理解反射这个点:

通过类型擦除实现,看代码(类型擦除分析点击我跳转

List<Integer> list = new ArrayList<>();
list.add(1);
Class clazz = list.getClass();
Method method = clazz.getMethod("add", Object.class);
// List<Integer> 里存储String字符串
method.invoke(list, "hello");
method.invoke(list, "你好");
for (Object o : list) {
    System.out.println(o);
}

结果:

1
hello
你好

这就是反射+类型擦除的作用。


三、反射的用法

学习反射的用法之前,先了解一下反射机制常用的类。

1.常用类

  • Java.lang.Class:Class对象
  • Java.lang.reflect.Constructor:构造器对象
  • Java.lang.reflect.Field:类的属性对象
  • Java.lang.reflect.Method:类的方法对象
  • Java.lang.reflect.Modifier:类的… …

2.基本的用法

使用

public native boolean isInstance(Object obj);

判断是否为某个类的实例。类似于instanceof的功能。

2.1 获取Class对象

本质就是获取到类对象的class字节码文件,JVM只能识别字节码文件,Java代码编译之后成为了字节码文件。Class获取的过程,就是由 .class -> .java 的过程。

  • Object.getClass()
  • Object.class:任何数据类型都有一个静态的class属性
  • Class.forName(String className):常用

还是以最上面的Hello类为例:

// 1.Object.getClass()
Hello h = new Hello();
Class clazz1 = h.getClass();

// 2.Object.class
Class clazz2 = Hello.class;

// 3.Class.forName(String className) 常用
Class clazz3 = Class.forName("com.pdh.bean.Hello");

由于Java的JVM不会重复加载类,所以以上三者获取到的Class对象是同一个。

下面的操作都是属于Class类的内置的方法,学会如何调用即可,具体的业务以具体的场景来定。


2.2 获取构造方法Constructor

获取构造方法就是为获取实体类的方法、属性等数据。

获取单个构造方法

Class类提供的方法

  • public Constructor getConstructor(Class… parameterTypes):获取单个的"公有" 的构造方法。
  • public Constructor getDeclaredConstructor(Class… parameterTypes):获取单个的 “私有、受保护、默认、公有” 的构造方法。
Class clazz = Class.forName("com.pdh.bean.Hello");
// 1.获取无参构造方法
Constructor constructor11 = clazz.getConstructor();         //公有
Constructor constructor12 = clazz.getDeclaredConstructor(); //私有、受保护、默认、公有
// 2.获取有参构造方法
Constructor constructor21 = clazz.getConstructor(String.class,Integer.class);//公有
Constructor constructor22 =
    clazz.getDeclaredConstructor(String.class,Integer.class);//私有、受保护、默认、公有
获取所有构造方法

Class类提供的方法

  • public Constructor[] getConstructors():所有"公有的"构造方法。
  • public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)。
Class clazz = Class.forName("com.pdh.bean.Hello");
// 1.获取所有 公有的 构造方法
Constructor[] constructors1 = clazz.getConstructors();
// 2.获取所有 私有、受保护、默认、公有 的构造方法
Constructor[] constructors2 = clazz.getDeclaredConstructors();

2.3 反射创建实例

Class类提供的方法

  • Class对象调用newInstance()创建:默认调用类的无参构造方法,已经过时。
  • Class对象获取构造器Constructor对象,在由Constructor对象调用newInstance()创建。
Class clazz = Class.forName("com.pdh.bean.Hello");
// 1.Class对象调用newInstance()创建
Object o1 = clazz.newInstance();
// 2.Class -> Constructor -> newInstance()
Object o2 = clazz.getConstructor().newInstance();

2.4 获取成员变量

Class类提供的方法

  • public Field getField(String name):获取指定的 共有的 单个成员变量。
  • public Field getDeclaredField(String name):获取指定的单个成员变量;
  • public Field[] getFields():获取 共有的 所有成员变量。
  • public Field[] getDeclaredFields():获取所有成员变量。
Class clazz = Class.forName("com.pdh.bean.Hello");
// 1.获取指定的单个成员变量
Field f11 = clazz.getField("data1");         //公有
Field f12 = clazz.getDeclaredField("data2"); //私有、受保护、默认、公有
// 2.获取所有成员变量
Field[] f21 = clazz.getFields();        //公有
Field[] f22 = clazz.getDeclaredFields();//私有、受保护、默认、公有

Field类的setAccessible(boolean flag)方法传递一个boolean值,true表示暴力访问,无论修饰符是什么。

Field类提供有大量的操作成员变量的方法,在Field类的加持下,可实现对成员变量的各种 操作。


2.5 获取成员方法

Class类提供的方法

  • public Method getMethod(String name, Class<?>… parameterTypes):获取指定的 共有的 内置方法,传递 方法名+方法形参字节码对象
  • public Method getDeclaredMethod(String name, Class<?>… parameterTypes):获取指定的内置方法,传递 方法名+方法形参字节码对象。。
  • public Method[] getMethods():获取所有的 共有的 内置方法。
  • public Method[] getDeclaredMethods():获取所有的内置方法。
Class clazz = Class.forName("com.pdh.bean.Hello");
// 1.获取指定的单个方法 传递 方法名+方法形参字节码对象
Method m11 = clazz.getMethod("hello",String.class,Date.class);  //公有
Method m12 = clazz.getDeclaredMethod("hello",String.class,Date.class);  //私有、受保护、默认、公有
// 2.获取类的所有方法
Method[] m21 = clazz.getMethods();          //公有
Method[] m22 = clazz.getDeclaredMethods();  //私有、受保护、默认、公有

对于Method类而言,它也提供非常多的API给我们操作类方法,最常用的就是 invoke(Object obj, Object... args) 方法(传递类对象+参数):

Class clazz = Class.forName("com.pdh.bean.Hello");
Method m11 = clazz.getMethod("hello",String.class,Date.class);
// invoke() 第一个参数为类对象,之后就是可变参数,传递的是对于方法的参数
m11.invoke(clazz.newInstance(),"张三",new Date());

如果调用的是类中的 static静态方法 ,invoke可以这样写:

Method.invoke(null, ... ...);

需要注意的一点:

invoke中的只传递两个参数的时候(即使类的方法只传递一个参数,比如main方法),对于第二个参数(即传递到类方法的参数),在jdk1.4时是使用一个数组传递多个参数(数组被拆分为多个参数),jdk1.5之后是可变参数,由于JDK向下兼容的原则,所以这里只是传递两个参数,且第二个参数为数组时,会以拆分数组作为多个参数传递到类的方法中。此时,就需要注意一下:

假设Hello类有方法show(需要传递数组参数):

public void show(String[] data){
    Arrays.stream(data).forEach(System.out::println);
}

那么我们调用的时候:

Class<Hello> cla = Hello.class;
Method m = cla.getMethod("show", String[].class);
m.invoke(cla.newInstance(),new String[]{"张三","李四"});

会报出Exception,意思是:参数个数错误。

Exception in thread "main" java.lang.IllegalArgumentException: wrong number of arguments

解决方法是把这个参数变为Object类型,或者在外再套一个数组:

// 1.把数组变为Object类型数据
m.invoke(cla.newInstance(),(Object) new String[]{"张三","李四"});
// 2.再套一个数组,拆分后还是会准确传递到方法
m.invoke(cla.newInstance(),new Object[]{new String[]{"张三","李四"}});

这里我是自行编写了一个方法只接收一个数组参数,出现了这个问题,同样的如果是main方法也是会出现这个问题,也需要进行处理,但是针对多参数的方法,就不会出现这个问题,因为已经指定了参数个数,JVM就不会再进行拆分。


四、反射实例

1.运行配置文件

看一个Spring中XML配置文件中的bean配置:

<bean id="User" class="com.pdh.entity.User"></bean>

这看起来是不是可以用反射很轻易的就可以实现:解析xml把xml里的内容作为参数,利用反射创建对象。

2.Spring

Spring中的很多功能都是运用反射技术实现的。比如耳熟能详的:
IoC(DI)、单元测试等。

3.序列化

序列化与反序列化技术也是基于反射才得以强大。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值