深入理解反射

一、类的加载、连接和初始化

这是jvm那块的知识,复习一下,这其实是和反射有关系的。

① 类加载

指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象(类是种定义,但在java里,万物都是对象,即类这种定义也是对象的一种),也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。

系统中所有的class类实际上也是实例,它们都是java.lang.Class的实例。

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

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

② 类的连接  

当类被加载之后,系统为之生成一个对应的Class对象,接着进入连接阶段。连接阶段负责把类的二进制数据合并到JRE中

三阶段验证、准备、解析,看https://www.cnblogs.com/NoYone/p/8989916.html 连接阶段

③ 类的初始化

初始化阶段的重要工作是执行类的初始化方法<clinit>,方法<clinit>是由编译器自动生成,主要是对静态Field进行初始化(赋值和静态代码块)

JVM初始化一个类包含如下几个步骤:

  1. 假如这个类还没有被加载和连接,则程序先加载并连接该类。
  2. 假如该类的父类还没有被初始化,则先初始化其父类。
  3. 假如类中有初始化语句(静态),则系统依次执行这些初始化语句。

由第二条可以推出,JVM最先初始化的总是java.lang.Object类。

注意:

  1. 调用类中的final常量(值在编译时可以确定),不会引起该类初始化(即使是static),因为它在编译其实是连接阶段被赋值,会被当成“宏变量”处理,使用常量时,常量会被不加思考替换成赋的值,所以无需初始化类。就是说在A类中  static final String a = "hello";   则A.a不引起初始化,而当static final String a = System.currentTimeMills() + ""; (编译时确定不了),则A.a会引起A类的初始化。
  2. 当使用ClassLoader的loadClass()方法来加载某个类时,该方法只是加载该类,应不会执行类的初始化
  3. 使用Class的forName()静态方法才会导致强制初始化该类。
  4. clinit方法只会执行一次
public class 类的初始化测试 {
    public static void main(String[] args) throws ClassNotFoundException {
        // 取得系统类加载器,默认的
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        // 下面语句仅仅加载Tester类
        cl.loadClass("Test");
        System.out.println("系统加载Tester类");
      Syttem.out.printLn("----加载完成----");
        // 下面语句才会初始化Tester类
        Class.forName("Test");
    }
}

public class Test {
    static {
        System.out.println("Test类的静态代码块");
    }
}

运行结果: JVM : -XX: TraceClassLoading

[Loaded DealReflect.类的初始化测试 from file:/E:/Intellij-Idea/fromXiaoXin/experiment/target/classes/]

[Loaded Test from file:/E:/Intellij-Idea/fromXiaoXin/experiment/target/classes/]

系统加载Tester类
----加载完成----
Test类的静态代码块

可以看出loadClass只加载,Class.forName才会引出初始化。

二、类加载器

类加载器负责将.class文件(可能在磁盘上,也可能是网络上)加载到内存中,并为之生成java.lang.Class对象。

  1. BootStrap ClassLoader(): 根类加载器,它并不是java.lang.ClassLoader的子类(所以从extClassLoader.getParent()是null),而是由JVM自身实现的(非Java)。 jre/lib/rt.jar
  2. Extension ClassLoade(ExtClassLoader extends URLClassLoader): 扩展类加载器,jre/lib/ext/jar
  3. System ClassLoader(AppClassLoader extends URLClassLoader): 系统类加载器,它负责在JVM启动时加载来自java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以系统类加载器作为父加载器。

注意:这里所说的父子关系不是继承上的父子关系,这里的父子关系是类加载器实例之间的关系(直接说运行顺序不完了。。)  URLClassLoader可以从文件或者网络中加载类。例如:file:  http:

类加载机制

  • 全盘负责
  • 父类委托。先让父加载器加载,父加载器加载不上自己再加载。
  • 缓存机制。缓存机制将加载过Class都缓存,当程序中需要使用某个Class时,类加载器先从缓存区搜寻该Class。这就是为什么修改了Class后需要重启JVM,修改才会被应用上。

    JVM中4种类加载器层次

三、通过反射查看类信息

① 获得Class对象

  • Class类的forName(String clazzName)静态方法。该字符串参数是某个类的全限定类名(要包含包名)
  • 调用某个类的class属性来获取到该类的Class对象。
  • 调用某个类的getClass()方法。该方法是java.lang.Object的方法,所有的Java对象都可以调用该方法。

建议用第二种,因为能在编译阶段就能判断需要访问的Class对象是否存在。

获取到类对象后,程序就可以调用Class对象的方法来获取该对象和该类的真实信息了。

② 从Class中获取信息

四、通过反射生成并操作对象

五、通过反射生成动态代理

在Java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口可以生成JDK动态代理类或动态代理对象。

Proxy提供了用于创建动态代理类和动态代理对象的方法,它是所有动态代理类的父类

执行代理对象的每个方法时都会被替换执行InvocationHandler对象的invoke方法。如何看生成的代理类里的代码(保存代理类过程在https://www.cnblogs.com/NoYone/p/8733868.html

注意super.h.invoke,上面也说了,Proxy是父类,h就是传给Proxy的InvocationHandler,调用invoke方法。

Proxy类的两个方法:

  • public static Class<?> getProxyClass(ClassLoader loader,
    Class<?>... interfaces):创建一个动态代理类对象的Class对象,该代理类将实现interfaces所指定的多个接口。第一个参数XXX.class.getClassLoader()
  • public static Object newProxyInstance(ClassLoader loader,
    Class<?>[] interfaces,
    InvocationHandler h):直接创建一个动态代理对象,该代理对象的实现类实现了interfaces指定的系列接口。

实际上,即是采用第一个方法获取了一个动态代理类之后,当程序需要通过该代理类来创建对象时一样需要传入一个InvocationHandler对象。也就是说,系统生成的每个代理类对象都有一个与之关联的InvocationHandler对象。即super.h.invoke(Object, Method, Object[])

小Demo看这里的就可以https://www.cnblogs.com/NoYone/p/8733868.html

posted @ 2019-02-20 12:09  NoYone  阅读(402)  评论(0编辑  收藏  举报