一、什么是反射
反射就是把Java类中的各个成员映射成一个个的Java对象。
即在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能调用它的任意一个方法和属性。这种动态获取信息及动态调用对象方法的功能叫Java的反射机制。
了解反射需要先了解java的虚拟机,即JVM。我们可以把它理解为一个进程,一个程序,它的作用就是跑你的代码。
名词解释:
• 內存:即JVM内存,栈、堆、方法区啥的都是JVM内存,只是人为划分
• .class文件:就是所谓的字节码文件,这里直观些成为 .class 文件
简单执行一段代码:
Object o = new Object();
对象的创建过程如下所示:
首先JVM会启动,代码会编译成一个.class字节码文件,然后被类加载器加载进JVM的内存中,类Object加载到方法区中,创建了Object类的Class对象到堆中,注意这个Class对象不是new出来的对象,而是类的类型对象。每个类只有一个Class对象,作为方法区类的数据结构的接口。jvm创建对象前,会先检查类是否加载,寻找类对应的class对象。若加载好,则为你的对象分配内存,初始化代码:new Object()
为什么要讲这个呢?因为要理解反射必须知道它在什么场景下使用。
Object对象是我们自己通过new出来的,是写死固定的代码放在JVM上跑的。假如,服务器在某个时刻遇到某个请求要调用其他的某个类,JVM中并没有加载这个类,就需要停止服务器,再写一段代码,重新启动服务器,特别麻烦。
我们有时需要动态的加载一些运行时才需要的一些类,用不到时,不需要加载到JVM。
很多项目的数据库,有时候需要用mysql,有时候需要用oracle,这就需要动态的加载对应的数据库驱动类。此时就可以使用反射来操作。
有以下两个类:com.java.dbtest.mysqlConnection
,com.java.dbtest.oracleConnection
在我们的项目中需要使用。程序可以写得比较动态化,通过
Class tc = Class.forName("com.java.dbtest.TestConnection");
即通过类的全类名让JVM在服务器中找到并加载这个类。如果想使用orcale,可以增加一个properties配置,获取该properties的配置信息,把orcale类的全类名当做参数传进去即可。
二、反射机制原理
- 反射机制允许程序在执行期借助于 ReflectionAPI取得任何类的内部信息(比如成员变量,构造器,成员方法等等),井能操作对象的属性及方法。反射在设计模式和框架底层都会用到
- 加載完类之后,在堆中就产生了ー个Cass类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为反射。
三、Java反射机制可以哪些
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时得到任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生成动态代理
四、反射相关的主要类
Java反射机制的实现要借助以下四个类:
- java.lang.Class:代表一个类, Class对象表示某个类加載后在堆中的对象
- java. lang.reflec.Method:代表类的方法,Method对象表示某个类的方法
- java.lng.reflect.Field:代表类的成员变量,Field对象表示,某个类的成员变量
- java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示构造器
五、如何使用反射
(1)实例化对象
- 通过Object类的getClass方法获得(基本不用)
package com.qinxue.demo;
class Person {}
public class TestDemo {
public static void main(String[] args) throwsException {
Person per = newPerson() ; // 正着操作
Class<?> cls = per.getClass() ; // 取得Class对象
System.out.println(cls.getName()); // 反着来
}
}
- 使用:"类.class"方式取得
package com.qinxue.demo;
class Person {}
public class TestDemo {
public static void main(String[] args) throwsException {
Class<?> cls = Person.class; // 取得Class对象
System.out.println(cls.getName()); // 反着来
}
}
- 使用Class类内部定义的static forName( )方法
package com.qinxue.demo;
class Person {}
public class TestDemo {
public static void main(String[] args) throwsException {
Class<?> cls = Class.forName("cn.mldn.demo.Person") ; // 取得Class对象
System.out.println(cls.getName()); // 反着来
}
}
(2)简单应用
传统的工厂模式代码
apackage com.qinxue.demo;
interface Fruit {
public void eat();
}
class Apple implements Fruit{
public void eat() {
System.out.println("吃苹果。");
}
}
class Factory {
public static Fruit getInstance (String className){
if ("apple".equals(className)) {
returnnewApple();
}
return null;
}
}
public class Factory Demo {
public static void main(String[]args){
Fruit f = Factory.getInstance("apple");
f.eat();
}
}
工厂设计模式之中有一个最大的问题:
如果现在接口的子类增加了,那么工厂类肯定需要修改,这是它所面临的最大问题,而这个最大问题造成的关键性的病因是new,那么如果说现在不使用关键字new了,变为了反射机制呢?
反射机制实例化对象的时候实际上只需要“包.类”就可以,于是根据此操作,修改工厂设计模式。
package com.qinxue.demo;
interface Fruit {
public void eat();
}
class Apple implements Fruit {
public void eat() {
System.out.println("吃苹果。");
}
}
class Orange implements Fruit {
public void eat() {
System.out.println("吃橘子。");
}
}
class Factory {
public static Fruit getInstance(String className) {
Fruit f = null;
try {
f = (Fruit) Class.forName(className).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return f;
}
}
public class Factory Demo {
public static void main(String[] args) {
Fruit f = Factory.getInstance("cn.mldn.demo.Orange");
f.eat();
}
}
这个时候即使增加了接口的子类,工厂类照样可以完成对象的实例化操作,这个才是真正的工厂类,可以应对于所有的变化。
如果单独从开发角度而言,与开发者关系不大,但是对于日后学习的一些框架技术这个就是它实现的命脉,在日后的程序开发上,如果发现操作的过程之中需要传递了一个完整的“包.类”名称的时候几乎都是反射机制作用。
六、反射优化
(1)关闭访问检查
- Method和 Field、 Constructor对象都有 setAccessible( )方法
- setAccessible作用是启动和禁用访问安全检查的开关
- 参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为 false则表示反射的对象执行访问检查
七、获取Class对象
- 已知一个类的全类名,且该类在类路径下,可通过 Class:类的静态方法forname0获取,可能抛出 Classnotfoundexception,实例:
Class cls1=Class forname("java. lang Cat");
多用于配置文件,读取类全路径,加载类。 - 若已知具体的类,通过类的 class获取,该方式最为安全可靠,程序性能最高实例: Class cls2=Cat. class; 多用于参数传递,比如通过反射得到对应构造器对象。
- 已知某个类的实例,调用该实例的 getclass(0方法获取Cass对象,实例:Class cla2=对象。 getclass( )。应用场景:通过创建好的对象,获取 Class对象。
- 其他方式
Classloader cl=car.getclass().getclassloader();
Class clazz4=cl. loadclass(“类的全类名”);
- 基本数据(int,char, boolean, float double,byte,long, short)按如下方式得到Class类
Class cls = 基本数据类型.class
- 基本数据类型对应的包装类,可以通过.type得到 Class类对象
Class cls = 包装类.TYPE
八、类的加载过程
- 加载阶段
JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件、也可能是jar包,甚至网络)转化为二进制字节流加载到内存中,井生成一个代表该类的java.lang.Class对象 - 连接阶段
1、验证
目的是为了确保 Class文件的字节流中包含的信息符合当前虚拟机的要求,井且不会危害虚拟机自身的安全
包括:文件格式验证(是否以魔数 oxcafebabe开头)、元数据验证、字节码验证和符号引用验证
可以考虑使用 -Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载
2、准备
JVM会在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始值如0、0L、null、 false等)。这些变量所使用的内存都将在方法区中进行分配
3、解析
符号引用替换为直接引用
(1)初始化阶段
到初始化阶段,オ真正开始执行类中定义的Java程序代码,此阶段是执行< clinit>( )方法的过程( )方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
虚拟机会保证一个类的( )方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类。那么只会有一个线程去执行这个类的( )方法,其他线程都需要阻塞等待,直到活动线程执行( )方法完毕。