栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

22.类加载器、单元测试和反射

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

22.类加载器、单元测试和反射

类加载的过程

    当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行加载。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。

 

加载

  加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象(类的类)(字节码对象),也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象(一个类只可能有一个字节码对象)。

现在 : 对象.getClass() == 类名.class --> 前面的对象是否属于后面的类型

以前的写法: 对象 instanceof 类    

   类的加载由类加载器(ClassLoader)完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类(类加载器的基类)来创建自己的类加载器。

通过使用不同的类加载器,可以从不同来源加载类的二进制数据(字节码文件),通常有如下几种来源。

  1. 从本地文件系统加载class文件,这是前面绝大部分示例程序的类加载方式。

  2. 从JAR包加载class文件,这种方式也是很常见的,JDBC编程时用到的数据库驱动类就放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。

  3. 通过网络加载class文件。 把一个Java源文件动态编译,并执行加载。

类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。 Object

链接

当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据(字节码对象)合并到JRE中。类连接又可分为如下3个阶段。

1)验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。Java是相对C++语言是安全的语言,例如Java有C++不具有的数组越界的检查。这本身就是对自身安全的一种保护。验证阶段是Java非常重要的一个阶段,它会直接的保证应用是否会被恶意入侵的一道重要的防线,越是严谨的验证机制越安全。验证的目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。其主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

四种验证做进一步说明:

文件格式验证:主要验证字节流(加载的字节码文件)是否符合Class文件格式规范,并且能被当前的虚拟机加载处理。例如:主,次版本号是否在当前虚拟机处理的范围之内。常量池中是否有不被支持的常量类型。指向常量的中的索引值是否存在不存在的常量或不符合类型的常量。

元数据验证:对字节码描述的信息进行语义的分析,分析是否符合java的语言语法的规范。

字节码验证:最重要的验证环节,分析数据流和控制,确定语义是合法的,符合逻辑的。主要的针对元数据验证后对方法体的验证。保证类方法在运行时不会有危害出现。

符号引用验证:主要是针对符号引用转换为直接引用的时候,是会延伸到第三解析阶段,主要去确定访问类型等涉及到引用的情况,主要是要保证引用一定会被访问到,不会出现类等无法访问的问题。

2)准备:类准备阶段负责为类的静态变量分配内存,并设置默认初始值(系统默认值)。

3)解析:将类的二进制数据中的符号引用替换成直接引用。说明一下:符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。

初始化

初始化是为类的静态变量赋予正确的初始值(程序员给静态成员变量赋的值),准备阶段和初始化阶段看似有点矛盾,其实是不矛盾的,

如果类中有语句:private static int a = 10,它的执行过程是这样的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,,然后到解析即a=0;到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。

总结:
    加载: 生成类的字节码对象
    链接:
        验证: 校验字节码对象中的成员是否符合Java类规范
        准备: 加载类中静态成员并赋予系统默认值
        解析: 把类中的符号引用替换成直接引用
    初始化: 把类中静态成员的真实值替换默认值;

类何时加载
  1. 创建类的实例,也就是new一个对象

  2. 访问某个类或接口的静态变量,或者对该静态变量赋值

  3. 调用类的静态方法

  4. 反射(Class.forName("全类名"))

  5. 初始化一个类的子类(会首先初始化子类的父类)

  6. JVM启动时标明的启动类,即文件名和类名相同的那个类 (main方法所在的类)

类何时被加载: 类第一次被使用的时候加载

类加载器

类加载器负责加载所有的类,其为所有被载入内存中的类生成一个java.lang.Class实例对象。一旦一个类被加载进入JVM中,同一个类就不会被再次载入了。(一个类只会被加载一次!)

  1. 根类加载器(bootstrap classloader):它用来加载 Java 的核心类,是用原生代码来实现的,并不继承自 java.lang.ClassLoader(负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。(无法获取到根类加载器对象)

  2. 扩展类加载器(extensions classloader):它负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为null。

  3. 系统类加载器(system class loader):被称为系统(也称为应用)类加载器,它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。

  4. 自定义加载器(custom class loader): 我要指定一些类由某个类加载器加载 那么就可以自定义类记载器

类加载机制-双亲委派模型

 

类加载器使用场景

类加载器的功能: 快速获取当前类所在模块src文件夹根目录下的文件 (新的相对路径)
    
ClassLoader: 类加载器的类(类加载器的基类)   
    static ClassLoader getSystemClassLoader() : 获取到系统类加载器对象
       
    InputStream getResourceAsStream(String fileName): 快速的获取传入文件路径指向的文件
        String fileName: 文件路径 --> 相对路径写: 相对路径的起点是src文件夹
            
static InputStream getSystemResourceAsStream(String name):直接获取输入流对象 -> 推荐方法

单元测试

单元测试 : 通过单独启动一个方法,测试方法内的逻辑或者结果是否正确 (白盒测试)
    
使用步骤:
    导入jar包,并注入到当前模块 (模块的根建libs文件夹,复制jar包,Add as Lib..)
        
    测试方法的规则: 不加static
        1. 方法名 : testXxxx
        2. 形参列表 : 必须无参 ()
        3. 返回值类型 : 必须 void
        4. 方法体内 : 逻辑代码

    断言操作 : 预估结果
        Assert.assertEquals(期望值, 真实值);

    @Before : 以前  --> 被修饰的方法一定会在测试方法之前执行一次
    @After : 以后   --> 被修饰的方法一定会在测试方法之后执行一次   

反射

反射 : 看懂后期的框架代码  //面向解决需求的人
    正射 : stu.study();
    反射 : study.invoke(stu);

反射第一步骤: 获取字节码对象

1. 类名.class : 任何类型都有class属性,通过class属性可以拿到此类型的字节码对象 (引用,基本,数组)
        Student.class , String.class  , int[].class , double.class
            
2. 对象.getClass() : 通过对象获取该对象所在类的字节码对象 (引用数据类型[基本数据类型使用包装类对象获取]) 
            
3. Class类(类的类)中 -> static Class forName("类型的全类名") : 传入一个类的全类名可以获取这个类的字节码对象          

反射第二步骤 : 获取类中的成员 构造方法 Constructor

Class类中的成员方法 :
    //只能拿public修饰的构造方法,可以看到父类的public修饰的构造方法对象
    Constructor[] getConstructors()  : 获取字节码对象中所有public修饰的构造方法对象
    Constructor getConstructor(Class... parameterTypes) : 获取字节码对象中指定的public修饰的构造方法对象   
    //Class... parameterTypes : 形参类型的字节码对象
    
    //不看权限修饰符的,只能拿到本类的拿不到父类的

    Constructor[] getDeclaredConstructors()  : 获取字节码对象中所有的构造方法对象 
    Constructor getDeclaredConstructor(Class... parameterTypes) 获取字节码对象中指定的的构造方法对象        

成员变量 Field

Class类中的成员方法 :
    //只能拿public修饰的构造方法,可以看到父类的public修饰的成员变量对象
    Field[] getFields()  : 获取字节码对象中所有public修饰的成员变量对象
    Field getField(String name) : 获取字节码对象中指定的public修饰的成员变量对象   
    //String name : 要获取的成员变量对象的变量名
    
    //不看权限修饰符的,只能拿到本类的拿不到父类的
    Field[] getDeclaredField()  : 获取字节码对象中所有的成员变量对象
    Field getDeclaredField(String name) : 获取字节码对象中指定的成员变量对象 

成员方法 Method

Class类中的成员方法 :
    //只能拿public修饰的构造方法,可以看到父类的public修饰的成员方法对象
    Method[] getMethods()  : 获取字节码对象中所有public修饰的成员方法对象
    Method getMethod(String methodName,Class... parameterTypes) : 获取字节码对象中指定的public修饰的成员方法对象   
    //String methodName : 方法名 Class... parameterTypes : 形参的字节码对象
    
    //不看权限修饰符的,只能拿到本类的拿不到父类的

    Method[] getDeclaredMethods()  : 获取字节码对象中所有的成员方法对象
    Method getDeclaredMethod(String methodName,Class... parameterTypes) :: 获取字节码对象中指定的成员方法对象 

反射的第三步 : 使用类中的成员

暴力反射开关 : 屏蔽权限  void setAccessible(boolean flag)
 只要成员没有访问权限,需要强行访问请开启暴力反射开关!!

构造方法

以前 : new 构造方法(实参)
    
现在 : 反射
    1. 拿到了构造方法对象
    2. 构造方法对象自启动
    
Constructor类中的方法:
    T newInstance(Object...args)
        
//Class类 : T newInstance() 前提 : Class对象所指向的类中必须有无参构造方法
        Student student = clazz.newInstance();
        System.out.println("student = " + student); 

成员变量

以前 : stu.属性名  / stu.属性名 = 值;

现在 : 反射 
    1. 拿到属性的对象
    2. 属性对象设置值和获取值
    
Field类中的方法:
    Object get(Object obj) : 获取调用方法的该属性的值
        Object : 接收的就是该属性的具体值
        Object obj : 哪个对象的该属性值
    void set(Object obj, Object value)  
        Object obj : 哪个对象的该属性值 
        Object value : 设置的值

成员方法

以前 : 返回值类型 返回值 = stu.方法名(实参);
    -> stu.study();

现在 : 反射
    1. 拿到方法的对象
    2. 方法对象自启动,传入调用方法的对象  study.invoke(stu);

Method类中的方法 : 启动方法对象指向的那个方法
     Object invoke(Object obj, Object... args) 
          返回值 Object : 被调用的方法的返回值 
              // 如果被启动的方法对象返回值是void,那么invoke方法的返回值是null
          形参列表 Object obj : 调用方法的对象
          形参列表 Object... args : 被启动的方法启动时需要的实际参数

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1031745.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号