深入理解java反射机制及应用 | 京东物流技术团队
因为最近项目中经常有java反射的使用,而其中的IOC、动态代理用到了反射,因此趁这个机会来总结一下关于Java反射的一些知识,复习一下。本篇基于JDK 1.8。
java反射机制是什么
反射原理
Java反射机制(Java Reflection) 是 Java 的特征之一,是Java语言中一种动态(运行时)访问、检测和修改它本身的能力,主要作用是动态(运行时)获取类的完整结构信息、调用对象的方法。简单点的说就是Java程序在运行时(动态)通过创建一个类的反射对象,再对类进行相关操作,比如:
-
在运行时判断任意一个对象所属的类;
-
在运行时构造任意一个类的对象;
-
在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
-
在运行时调用任意一个对象的方法
重点: 是运行时而不是编译时
多数情况下,我们使用某个类,都会知道这个类,以及要用它来做什么,可以直接通过 new 实例化创建对象,然后使用这个对象对类进行操作,这个就属于正射。反射则是一开始并不知道要初始化的是什么类,无法使用new来实例化创建对象,主要是通过JDK提供的反射API来实现,在运行时才知道要操作的是什么类,并且可以获取到类的完整构造以及调用对应的方法,这就是反射。
举例:
public class Dog {
private int id;
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public static void main(String[] args) throws Exception{
//一、正射调用过程
Dog dog = new Dog();
dog.setId(1);
System.out.println("这是一个正射调用过程Dog id:" + dog.getId());
//二、反射调用过程
Class clz = Class.forName("com.learning.java.Dog");
Constructor dogConstructor = clz.getConstructor();
Object dogObj = dogConstructor.newInstance();
//方法调用
Method setIdMethod = clz.getMethod("setId", int.class);
setIdMethod.invoke(dogObj, 2);
Method getIdMethod = clz.getMethod("getId");
System.out.println("这是一个反射调用过程Dog id:" + getIdMethod.invoke(dogObj));
}
}
输出结果:
这是一个正射调用过程Dog id:1
这是一个反射调用过程Dog id:2
Java 语言是一种面向对象的语言,在面向对象的世界里,万事万物皆对象,那我们写的 class 类也是对象,他们都是 java.lang.Class 的对象。我们在写类的时候,并没有显式的写这个对象,我们写的类会编译成一个类,生成一个 class 文件,而编译器就把 java.lang.Class 的这个对象存放在 class 文件的末尾,里面保存了类的元数据信息,这些元数据信息都包括类的所有信息,比如它是类还是接口、集成和实现了哪些类和接口,有什么属性,有什么方法,我们在 new 一个对象的时候,可以 new 很多对象,但是这个类生成的 class 对象只能有一个(在不同的类加载器,可能有多个,这里涉及到虚拟机的知识了)。我们在实例化 Dog 这个的类对象的时候,虚拟机会去检查,在虚拟机里面,这个类有没有被加载过,如果没有,虚拟机会先加载 Dog 对应的这个 class 对象,加载完之后,才会轮到 Dog 实例化本身的对象。
获取类的java.lang.Class实例对象常见的三种方式
获取类的java.lang.Class实例对象,常见的三种方式分别为:
-
通过 ObjectClass.class 获取,这里的 ObjectClass 指具体类,JVM 会使用 ClassLoader 类加载器将类加载到内存中,但并不会做任何类的初始化工作,返回 java.lang.Class 对象。
-
通过 Class.forName ("类的全局定名")获取,全局定名为包名+类名,类会被 JVM 加载到内存中,并且会进行类的静态初始化工作,返回 java.lang.Class 对象。
-
通过 new 通过 ObjectClass().getClass() 获取,这里的 ObjectClass 指具体类,使用了 new 进行实例化操作,因此静态初始化和非静态初始化工作都会进行,getClass 方法属于顶级 Object 类中的方法,任何子类对象都可以调用,调用时返回子类的 java.lang.Class 对象。
这几种方式,最终在JVM堆区对应类的 java.lang.Class 对象都属于同一个,也就是内存地址相同,进行双等号比较结果为 true,原因是 JVM 类加载过程中使用的是同一个 ClassLoader 类加载器加载某个类,不论加载多少次,生成到堆区的 java.lang.Class 对象始终只有一个,除非自定义类加载器,破坏 JVM 的双亲委派机制,使得同一个类被不同类加载器加载,JVM 才会把它当做两个不同的 java.lang.Class 对象。
下面创建一个实体类,分别在实体类中创建类的静态代码块、动态代码块、有参构造方法、无参构造方法,方便测试几种方式的区别及内存地址是否相同
public class ObjectClass {
private static final String staticStr = "Hi";
private static int staticInt = 2024;
private static Class<?> class1;
private String id;
static {
System.out.println("静态代码块:staticStr=" + staticStr + ",staticInt=" + staticInt);
}
{
System.out.println("动态代码块~");
}
public ObjectClass() {
System.out.println("无参构造方法~");
}
public ObjectClass(String id) {
System.out.println("有参构造方法~");
this.id = id;
}
public void setId(String id) {
this.id = id;
}
public static void main(String[] args) throws ClassNotFoundException {
System.out.println("1=====================================");
System.out.println("一、ObjectClass.class方式=========");
Class<?> class1 = ObjectClass.class;
System.out.println("2=====================================");
System.out.println("二、Class.forName方式=========");
Class class2 = Class.forName("cn.learning.java.reflect.classtest.test.ObjectClass");
System.out.println("3=====================================");
System.out.println("三、new ObjectClass().getClass方式=========");
Class class3 = new ObjectClass().getClass();
System.out.println("11=====================================");
System.out.println("一、ObjectClass.class方式=========");
Class<?> class11 = ObjectClass.class;
System.out.println("二、Class.forName方式=========");
Class class12 = Class.forName("cn.learning.java.reflect.classtest.test.ObjectClass");
System.out.println("22=====================================");
System.out.