java反射原理的概念java反射机制的核心原理java反射机制原理详解




java反射原理的概念java反射机制的核心原理java反射机制原理详解

2022-07-21 2:24:07 网络知识 官方管理员


一、java的核心机制

java有两种核心机制:java虚拟机(JavaVirtualMachine)与垃圾收集机制(Garbagecollection):

1、Java虚拟机:是运行所有Java程序的抽象计算机,是Java语言的运行环境,在其上面运行Java代码编译后的字节码程序,java虚拟机实现了平台无关性。

2、Java垃圾回收(GarbageCollection):自动释放不用对象内存空间,在java程序运行过程中自动进行,垃圾收集机制可大大缩短编程时间,保护程序的完整性,是Java语言安全性策略的一个重要部份。

二、java虚拟机及其结构

java垃圾回收不需要程序员手动操作,我们经常需要关注的是java虚拟机,java虚拟机承载着程序从源码到运行的全部工作。

Java虚拟机是可运行Java代码的假想计算机,有自己想象中的硬件,如处理器、堆栈、寄存器等,还具有相应的指令系统,可以执行Java的字节码程序。Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。Java语言使用模式Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。

对于JVM的基本结构,我们可以从下图可以大致了解:

java反射原理的概念(java反射机制的核心原理)(1)

三、程序的运行过程

从源文件创建到程序运行,Java程序要经过两大步骤:编译,运行;1、源文件由编译器编译成字节码(ByteCode)2、字节码由java虚拟机解释运行。

1、第一步(编译):创建完源文件之后,程序会被编译器编译为.class文件。Java编译一个类时,如果这个类所依赖的类还没有被编译,编译器就会先编译这个被依赖的类,然后引用,否则直接引用。。编译后的字节码文件格式主要分为两部分:常量池和方法字节码。

2、第二步(运行):java类运行的过程大概可分为两个过程:1、类的加载2、执行。

四、类的加载

类加载过程

java程序经过编译后形成.class文件。通过类加载器将字节码(.class)加载入JVM的内存中。JVM将类加载过程分成加载,连接,初始化三个阶段,其中连接阶段又可分为验证,准备,解析三个阶段。

JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:

java反射原理的概念(java反射机制的核心原理)(2)

1、BootstrapClassLoader启动类加载器

负责加载$JAVA_HOME中jre/lib/里所有的class(JDK代表JDK的安装目录,下同),或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被BootstrapClassLoader加载)。启动类加载器由C++实现,不是ClassLoader子类。无法被Java程序直接引用的。

2、ExtensionClassLoader扩展类加载器

该加载器由sun.misc.LauncherExtClassLoader实现,负责加载Java平台中扩展功能的一些jar包,包括ExtClassLoader实现,负责加载Java平台中扩展功能的一些jar包,包括JAVA_HOME中jre/lib/.jar或-Djava.ext.dirs指定目录下的jar包。即JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.开头的类),开发者可以直接使用扩展类加载器

3、AppClassLoader应用程序类加载器

该类加载器由sun.misc.Launcher$AppClassLoader来实现,负责记载classpath中指定的jar包及目录中class,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

启动类加载器:它使用C++实现(这里仅限于Hotspot,也就是JDK1.5之后默认的虚拟机,有很多其他的虚拟机是用Java语言实现的),是虚拟机自身的一部分。

所有其他的类加载器:这些类加载器都由Java语言实现,独立于虚拟机之外,并且全部继承自抽象类java.lang.ClassLoader,这些类加载器需要由启动类加载器加载到内存中之后才能去加载其他的类。

应用程序都是由这三种类加载器互相配合进行加载的,我们还可以加入自定义的类加载器。

加载

加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:

1、通过一个类的全限定名来获取其定义的二进制字节流。

2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

注意,这里第1条中的二进制字节流并不只是单纯地从Class文件中获取,比如它还可以从Jar包中获取、从网络中获取(最典型的应用便是Applet)、由其他文件生成(JSP应用)等。

相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。

(JVM主要在程序第一次主动使用类的时候,才会去加载该类。也就是说,JVM并不是在一开始就把一个程序就所有的类都加载到内存中,而是到用的时候才把它加载进来,而且只加载一次。)

加载过程中会先检查类是否被已加载,检查顺序是自底向上,从CustomClassLoader到BootStrapClassLoader逐层检查,只要某个Classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

这几种类加载器的层次关系如下图所示:

java反射原理的概念(java反射机制的核心原理)(3)

这种层次关系称为类加载器的双亲委派模型。双亲委派模型的工作流程是:

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

验证

验证的目的是为了确保Class文件中的字节流包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。不同的虚拟机对类验证的实现可能会有所不同,但大致都会完成以下四个阶段的验证:文件格式的验证、元数据的验证、字节码验证和符号引用验证。

准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:

1、这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。

2、这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。

解析

解析阶段是虚拟机将常量池中的符号引用转化为直接引用的过程。

解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行,分别对应于常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info四种常量类型。

1、类或接口的解析:判断所要转化成的直接引用是对数组类型,还是普通的对象类型的引用,从而进行不同的解析。

2、字段解析:对字段进行解析时,会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段,如果有,则查找结束;如果没有,则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口,还没有,则按照继承关系从上往下递归搜索其父类,直至查找结束。

初始化

类初始化是类加载过程的最后一个阶段,到初始化阶段,才真正开始执行类中的Java程序代码。虚拟机规范严格规定了有且只有四种情况必须立即对类进行初始化:

1、遇到new、getstatic、putstatic、invokestatic这四条字节码指令时,如果类还没有进行过初始化,则需要先触发其初始化。生成这四条指令最常见的Java代码场景是:使用new关键字实例化对象时、读取或设置一个类的静态字段(static)时(被static修饰又被final修饰的,已在编译期把结果放入常量池的静态字段除外)、以及调用一个类的静态方法时。

2、使用Java.lang.refect包的方法对类进行反射调用时,如果类还没有进行过初始化,则需要先触发其初始化。

3、当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。

4、当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先执行该主类。

虚拟机规定只有这四种情况才会触发类的初始化,称为对一个类进行主动引用,除此之外所有引用类的方式都不会触发其初始化,称为被动引用。

五、静态加载和动态加载

Java初始化一个类的时候可以用new操作符来初始化,也可通过Class.forName的方式来得到一个Class类型的实例,然后通过这个Class类型的实例的newInstance来初始化.我们把前者叫做JAVA的静态加载,把后者叫做动态加载.。

有时候我们说某个语言具有很强的动态性,有时候我们会区分动态和静态的不同技术与作法。我们朗朗上口动态绑定(dynamicbinding)、动态链接(dynamiclinking)、动态加载(dynamicloading)等。然而“动态”一词其实没有绝对而普遍适用的严格定义,有时候甚至像面向对象当初被导入编程领域一样,一人一把号,各吹各的调。

一般而言,开发者社群说到动态语言,大致认同的一个定义是:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。

尽管在这样的定义与分类下Java不是动态语言,它却有着一个非常突出的动态相关机制:Reflection。Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。

1、静态加载的时候如果在运行环境中找不到要初始化的类,抛出的是NoClassDefFoundError,它在JAVA的异常体系中是一个Error.

2、动态态加载的时候如果在运行环境中找不到要初始化的类,抛出的是ClassNotFoundException,它在JAVA的异常体系中是一个checked异常,在写代码的时候就需要catch.

六、反射

JAVA反射机制:

在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

Java反射机制主要提供了以下功能:

在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。

Java有个Object类,是所有Java类的继承根源,其内声明了数个应该在所有Java类中被改写的方法:hashCode()、equals()、clone()、toString()、getClass()等。其中getClass()返回一个Class对象。

【Class类十分特殊。它和一般类一样继承自Object,其实体用以表达Java程序运行时的classes和interfaces,也用来表达enum、array、primitiveJavatypes(boolean,byte,char,short,int,long,float,double)以及关键词void。当一个class被加载,或当加载器(classloader)的defineClass()被JVM调用,JVM便自动产生一个Class对象。】

如果您想借由“修改Java标准库源码”来观察Class对象的实际生成时机(例如在Class的constructor内添加一个println()),这样是行不通的!因为Class并没有publicconstructor。

Class是Reflection故事起源。针对任何您想探勘的类,唯有先为它产生一个Class对象,接下来才能经由后者唤起为数十多个的ReflectionAPIs。Reflection机制允许程序在正在执行的过程中,利用ReflectionAPIs取得任何已知名称的类的内部信息,包括:package、typeparameters、superclass、implementedinterfaces、innerclasses、outerclasses、fields、constructors、methods、modifiers等,并可以在执行的过程中,动态生成instances、变更fields内容或唤起methods。

从Class中获取信息

Class类提供了大量的实例方法来获取该Class对象所对应的详细信息,Class类大致包含如下方法,其中每个方法都包含多个重载版本,因此我们只是做简单的介绍,详细请参考JDK文档

获取类内信息

获取内容方法签名构造器Constructor<T>getConstructor(Class<?>...parameterTypes)包含的方法MethodgetMethod(Stringname,Class<?>...parameterTypes)包含的属性FieldgetField(Stringname)包含的Annotation<AextendsAnnotation>AgetAnnotation(Class<A>annotationClass)内部类Class<?>[]getDeclaredClasses()外部类Class<?>getDeclaringClass()所实现的接口Class<?>[]getInterfaces()修饰符intgetModifiers()所在包PackagegetPackage()类名StringgetName()简称StringgetSimpleName()

一些判断类本身信息的方法

判断内容方法签名注解类型?booleanisAnnotation()使用了该Annotation修饰?booleanisAnnotationPresent(Class<?extendsAnnotation>annotationClass)匿名类?booleanisAnonymousClass()数组?booleanisArray()枚举?booleanisEnum()原始类型?booleanisPrimitive()接口?booleanisInterface()obj是否是该Class的实例booleanisInstance(Objectobj)

(1)获取class

java反射原理的概念(java反射机制的核心原理)(4)

主有三种获得class的途径,使用时要注意区别

1、a,类型.class如:String.class使用类名加“.class”的方式即会返回与该类对应的Class对象。这个方法可以直接获得与指定类关联的Class对象,而并不需要有该类的对象存在。

2、b,Class.forName("类名");该方法可以根据字符串参数所指定的类名获取与该类关联的Class对象。如果该类还没有被装入,该方法会将该类装入JVM。forName方法的参数是类的完整限定名(即包含包名)。通常用于在程序运行时根据类名动态的载入该类并获得与之对应的Class对象。

3、c,obj.getClass();所有Java对象都具备这个方法,该方法用于返回调用该方法的对象的所属类关联的

(2)获取构造方法

Class类提供了四个public方法,用于获取某个类的构造方法:

1、ConstructorgetConstructor(Class[]params)根据构造函数的参数,返回一个具体的具有public属性的构造函数

2、ConstructorgetConstructors()返回所有具有public属性的构造函数数组

3、ConstructorgetDeclaredConstructor(Class[]params)根据构造函数的参数,返回一个具体的构造函数(不分public和非public属性)

4、ConstructorgetDeclaredConstructors()返回该类中所有的构造函数数组(不分public和非public属性)

  • 1反射出无参的构造方法并得到对象
  • *注意:
  • *1在Class.forName()中应该传入含有包名的类全名
  • *2newInstance()方法的本质是调用类的无参Public构造方法
  • /
  • StringclassName1="cn.testreflect.Worker";
  • Classclazz1=Class.forName(className1);
  • Objectobject1=clazz1.newInstance();
  • System.out.println("object1.toString()="+object1.toString());
  • /*
  • *2反射出带参数的构造方法并得到对象
  • */
  • StringclassName2="cn.testreflect.Worker";
  • Classclazz2=Class.forName(className2);
  • Constructorconstructor1=clazz2.getConstructor(int.class,String.class);
  • Objectobject2=constructor1.newInstance(18,"小明");
  • System.out.println("object2.toString()="+object2.toString());
  • (3)获取类的成员方法
  • 与获取构造方法的方式相同,存在四种获取成员方法的方式: 
  • 1、MethodgetMethod(Stringname,Class[]params)根据方法名和参数,返回一个具体的具有public属性的方法
  • 2、Method[]getMethods()返回所有具有public属性的方法数组
  • 3、MethodgetDeclaredMethod(Stringname,Class[]params)根据方法名和参数,返回一个具体的方法(不分public和非public属性)
  • 4、Method[]getDeclaredMethods()返回该类中的所有的方法数组(不分public和非public属性)
*调用对象的带参数的方法  */  StringclassName5="cn.testreflect.Worker";  Classclazz5=Class.forName(className5);  Methodmethod=clazz5.getMethod("printMessage",String.class,int.class,int.class);  Objectobject5=clazz5.newInstance();  method.invoke(object5,"周星星",50,9527);  }catch(Exceptione){  System.out.println(e.toString());  }

(4)获取类的成员变量(成员属性)

存在四种获取成员属性的方法

1、FieldgetField(Stringname)根据变量名,返回一个具体的具有public属性的成员变量

2、Field[]getFields()返回具有public属性的成员变量的数组

3、FieldgetDeclaredField(Stringname)根据变量名,返回一个成员变量(不分public和非public属性)

4、Field[]getDelcaredFields()返回所有成员变量组成的数组(不分public和非public属性)

*1获取类的私有字段  *注意:  *获取共有字段应调用clazz3.getField(name)方法  */  StringclassName3="cn.testreflect.Worker";  Classclazz3=Class.forName(className3);  FieldageField1=clazz3.getDeclaredField("age");  System.out.println("ageField1="+ageField1);  /**  *2获取和更改某个对象的私有字段  *即模拟get()和set()方法  */  StringclassName4="cn.testreflect.Worker";  Classclazz4=Class.forName(className4);  FieldageField2=clazz4.getDeclaredField("age");  Objectobject4=constructor1.newInstance(18,"小明");  //取消访问私有字段的合法性检查  ageField2.setAccessible(true);  //获取对象的私有字段  ObjectageObject4=ageField2.get(object4);  System.out.println("ageObject4="+ageObject4);  //再更改对象的私有字段的值  ageField2.set(object4,9527);  //重新获得  ObjectageObject5=ageField2.get(object4);  System.out.println("ageObject5="+ageObject5);


发表评论:

最近发表
网站分类
标签列表