zoukankan      html  css  js  c++  java
  • 深入理解反射

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

    这是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

  • 相关阅读:
    路径查看linux 向内核注册总线例子
    属性应用Android Manifest之<provider>元素中文注释
    序数序列hdu 1394
    大分类分类Zen Cart大分类下直接显示产品列表插件
    虚拟化运行[OpenStack] VMWare产品介绍
    名称磁盘Linux系统监控的CPU、Mem、IO的OID
    端口服务器黑马韩前成linux从入门到精通proftpd服务器配置
    主库配置关于Dataguard Online redo log 和 Standby redo log
    尺寸品牌Jquery 仿淘宝京东多条件筛选 可自行结合ajax加载
    设置编辑(iPhone/iPad开发)设置UITextView为不可编辑状态
  • 原文地址:https://www.cnblogs.com/NoYone/p/10398011.html
Copyright © 2011-2022 走看看