java中的反射原理,为什么要使用反射以及反射使用场景
什么是反射
反射是框架的灵魂
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
在java中获取字节文件的方式有三种
任何数据类型(包括基本数据类型)都有一个“静态”的class属性
Object(对象) ——> getClass();
通过Class类的静态方法:forName(String className)(常用)
//方法一
Class<CarEntity> 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的注解式事务是怎么实现的?? 现在我们大概可以猜猜了(只是猜想):
1.通过注解,我们在项目启动的时候可以获取所有打了注解的类或方法
2.通过反射,我们可以获取类的所有信息或方法的所有信息
3.通过反射,我们可以在方法的前后加上事务回滚相关的代码,然后通过上面例子中的invoke方法调用目标方法
4.这个过程我不需要知道你这些类或方法是干嘛的,你的一切与我无关