java中的反射原理,为什么要使用反射以及反射使用场景
什么是反射
反射是框架的灵魂
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
在java中获取字节文件的方式有三种
- 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
- Object(对象) ——> getClass();
- 通过Class类的静态方法:forName(String className)(常用)
//方法一 Class carEntityClass0 = CarEntity.class; //方法二 CarEntity carEntity =new CarEntity(); Class carEntityClass1 =carEntity.getClass(); //方法三 Class carEntityClass2 = Class.forName("com.example.demo3.Entity.CarEntity"); //判断获取到同一个类的Class对象是否是同一个 System.out.println(carEntityClass0 == carEntityClass1); System.out.println(carEntityClass1 == carEntityClass2); System.out.println(carEntityClass0 == carEntityClass2);
上面的例子得到的结果,是三个true,由此我们得到了第一个定理:
在运行期间,一个类,只有一个Class对象产生
三种方式常用第三种,第一种需要导入类的包,依赖太强,不导包就抛编译错误。第二种对象都有了还要反射干什么。一般都第三种,一个字符串可以传入也可写在配置文件中等多种方法(框架中都是用的第三种)。
好,现在我们得到了Class对象了,又有什么用呢,Class对象是什么呢,能做什么呢?
在此之前我们先了解一下正常情况下我们new一个对象的时候,jvm底层做了什么事情。
首先要搞明白一件事情,jvm能读懂我们的java代码吗?不能!
那jvm是靠读取什么东西来运行程序的呢?.class文件!
请放大看下图。。。。
也就是说,我们现在可以不通过JVM的编译直接获取到jvm运行时需要的Class对象!
也就是说!我们是不是可以通过对Class对象进行修改而改变CarEntity这个类原本在jvm里运行的逻辑!从而达到一系列不可告人的目的呢?
没错,我们可以,这就像同桌张三把作业给我让我帮忙交给老师,然后我直接把他的作业全部撕了然后告诉老师(JVM):张三这个崽种没做作业!(这是后面要讲的代理模式)。在当前的反射篇章我们可以理解为,我可以得到张三的作业的所有答案,然后我拿着自己用!
好,例子来了,顺便我们熟悉一下Class对象的常用API,面试的时候就可以装逼了
先看看我们的实体类是什么样子的
//一个public 属性 public String name; //一个private 属性 private String price; //一个public 构造方法 public CarEntity(String name, String price) { this.name = name; this.price = price; } //一个private 构造方法 private CarEntity(String name){ this.name = name; } //以下全都是public 的GET,SET方法 public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPrice() { return price; } public void setPrice(String price) { this.price = price; }
好!开始测试!
public static void main(String[] args) throws Exception { //获取CarEntity的Class对象 Class carEntityClass = Class.forName("com.example.demo3.Entity.CarEntity"); System.out.println("获取所有的Public的成员变量"); Field[] field = carEntityClass.getFields(); for (Field field1 : field) { System.out.println(field1.getName()); } System.out.println("获取所有的的成员变量,不管你是Public,Private,Protected还是Default "); Field[] field01 = carEntityClass.getDeclaredFields(); for (Field field1 : field01) { System.out.println(field1.getName()); } }
看看结果是什么
获取所有的Public的成员变量 name 获取所有的的成员变量,不管你是Public,Private,Protected还是Default name price
好,再来一个
System.out.println("获取所有的Public的构造方法"); Constructor[] constructors = carEntityClass.getConstructors(); for (Constructor constructor1 : constructors) { System.out.println(constructor1); } System.out.println("获取所有的的构造方法,不管你是Public,Private,Protected还是Default "); Constructor[] constructors01 = carEntityClass.getDeclaredConstructors(); for (Constructor constructor1 : constructors01) { System.out.println(constructor1); }
结果:
获取所有的Public的构造方法 public com.example.demo3.Entity.CarEntity(java.lang.String,java.lang.String) 获取所有的的构造方法,不管你是Public,Private,Protected还是Default public com.example.demo3.Entity.CarEntity(java.lang.String,java.lang.String) private com.example.demo3.Entity.CarEntity(java.lang.String)
发现了没?我们现在只需要一个类的全路径,我们就可以掌握这个类的所有情况!
上面的例子我们也发现了Class对象的APi的规律,只要加了Declared的Get方法,我们就能够“非法”地获取到这个类的编写者本来不愿意公布出来的属性!
当然我们还可以获取到这个类的所有普通方法:
System.out.println("获取所有的方法"); Method[] methods = carEntityClass.getMethods(); for (Method method : methods) { System.out.println(method.getName()); }
获取所有的方法 getName setName getPrice setPrice wait wait wait equals toString hashCode getClass notify notifyAll
我们再继续深入一点点,大家耐心看。
我们先给我们的Car类补上刚刚忘掉的无参构造方法
public CarEntity() { }
然后开始我们的测试**(是干嘛呢?通过反射调用目标类的方法!)
//获取CarEntity的Class对象 Class carEntityClass = Class.forName("com.example.demo3.Entity.CarEntity"); //通过Class对象获取到具体的CarEntity实例(需要无参构造方法!!!!) CarEntity carEntity = (CarEntity)carEntityClass.newInstance(); System.out.println("获取SetName方法"); //第一个参数:方法名称,第二个参数:方法形参的类型 Method method = carEntityClass.getDeclaredMethod("setName",String.class); //第一个参数,对象类型carEntity,第二个参数是我这里调用方法时传的参数 method.invoke(carEntity,"张三"); System.out.println("获取getName方法"); Method method2 = carEntityClass.getDeclaredMethod("getName",null); String name = (String) method2.invoke(carEntity,null); System.out.println(name);
获取SetName方法 获取getName方法 张三
我们现在居然只通过一个类的路径,获取到了这个类的所有信息,并且还能调用他的所有方法。
现在是不是大概明白了,为什么一开始说反射是框架的灵魂。举个最简单的例子,Spring的注解式事务是怎么实现的?? 现在我们大概可以猜猜了(只是猜想):
- 通过注解,我们在项目启动的时候可以获取所有打了注解的类或方法
- 通过反射,我们可以获取类的所有信息或方法的所有信息
- 通过反射,我们可以在方法的前后加上事务回滚相关的代码,然后通过上面例子中的invoke方法调用目标方法
- 这个过程我不需要知道你这些类或方法是干嘛的,你的一切与我无关
框架就是这样诞生的,更多的细节请看我的其他博客,关于静态代理和动态代理。