Java反射、类的动态加载

Java反射

Oracle 官方对反射的解释是:

Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine.This is a relatively advanced feature and should be used only by developers who have a strong grasp of the fundamentals of the language. With that caveat in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible.

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

正射

存在说反射,那么相对应的也就会有“正射"

那么“正射"的概念是啥?

其实在我们编写代码的时候,每当我们需要用到某一个类的时候,首先肯定需要实例化这么类,然后用实例化好的对象进行操作,这个过程其实就是正射。

//正射
Animals animal = new Animals();
animal.shout("狗");

//狗叫

反射

反射就是我们一开始不知道要初始化的类对象是啥,所以也没办法直接将对象给new出来

//反射
Class<?> clazz = Class.forName("serialize.Animals");
System.out.println(clazz.toString());
Method shout = clazz.getMethod("shout", String.class);
System.out.println(shout.toString());
Constructor<?> constructor = clazz.getConstructor();
System.out.println(constructor.toString());
Object o = constructor.newInstance();
System.out.println(o.toString());
shout.invoke(o,"猫");

//class serialize.Animals
//public void serialize.Animals.shout(java.lang.String)
//public serialize.Animals()
//serialize.Animals@4554617c
//猫叫

从输出的流程大致可以看出反射的调用是,首先从serialize.Animals获取到要操作的类对象,然后在获取需要执行的方法

对比

两种方式的执行效果的是一样的,当时在实现的过程还是有挺大区别的

image-20220317211333749

  • 正射代码在未运行前就已经知道了要运行的类是Animals
  • 反射代码则是到整个程序运行的时候,从字符串reflection.Animals,才知道要操作的类是Animals

反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

类的动态加载

类加载

类加载的时机

类加载,即虚拟机加载.class文件。什么时候虚拟机需要开始加载一个类呢?虚拟机对此没有规范约束,交给虚拟机把握。

Javac原理

javac是用于将源码文件.java编译成对应的字节码文件.class。

其步骤是:源码——>词法分析器组件(生成token流)——>语法分析器组件(语法树)——>语义分析器组件(注解语法树)——>代码生成器组件(字节码)。

类加载过程

先在方法区找class信息,有的话直接调用,没有的话则使用类加载器加载到方法区(静态成员放在静态区,非静态成功放在非静态区),静态代码块在类加载时自动执行代码,非静态的不执行;先父类后子类,先静态后非静态;静态方法和非静态方法都是被动调用,即不调用就不执行。

类加载的流程如下图:

image-20211110190140776

  • 加载

    • 通过类的全名限定获取定义此类的二进制字节流。
    • 将字节流代表的静态存储结构转化为方法区的运行时数据结构。
    • 在内存(方法区)生成一个代表这个类的class对象,作为方法区这个类的各种数据访问入口。

    加载和连接是交叉进行的,加载未完成可能连接已经开始了。

  • 验证

    检查class是否符合要求,非必须阶段,对程序的运行期没有影响,-Xverif:none 关闭(可以提高启动速度)

    • 文件格式验证(魔数、常量类型);

    • 元数据验证(语义分析);

    • 字节码验证(语义合法);

    • 符号引用验证;

  • 准备

    正式为类变量(static成员变量)分配内存并设置类变量初始值(零值)的阶段,这个变量所使用的内存都将在方法区进行分配,这时候的内存分配仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起在堆中进行分配。

  • 解析

    虚拟机将常量池内的符号引用替换为直接引用的过程。

  • 初始化

    初始化阶段是执行类构造器\()方法的过程,虚拟机会保证一个类的类构造器\()在多线程环境中被正确的加锁,同步;如果多个线程同时初始化一个类,那么只会有一个线程区执行这个类的类构造器,其他线程阻塞等待,直到\()方法完毕,同一个类加载器,一个类只会被初始化一次。

代码分析

  • 类加载的时候会执行代码
  • 初始化:静态代码块
  • 实例化:构造代码块、无参构造函数

构建一个Person类

package loadclassTest;

import java.io.Serializable;

public class Person implements Serializable {
    public String name;
    private int age;

    public static int id;

    static {
        System.out.println("静态代码块");
    }

    public static void staticMethod(){
        System.out.println("静态方法");
    }

    {
        System.out.println("构造代码块");
    }

    public Person() {
        System.out.println("无参构造");
    }

    public Person(String name, int age) {
        System.out.println("有参构造");
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

测试

new Person(); //输出  静态代码块  构造代码块  无参构造
new Person("a", 18); //输出 静态代码块  构造代码块  有参构造
Person.staticMethod();  //输出 静态代码块  静态方法
Person.id = 1; //输出 静态代码块

可以看到无论是无参构造、有参构造、调用静态方法还是给静态变量赋值都会调用静态代码块

尝试直接调用class加载类Class c = Person.class;,发现没有输出,没有进行初始化只进行了加载

动态加载

动态类加载方法:Class.forname

使用Class.forName来动态加载类

Class<?> c = Class.forName("serialize.ClassLoadText.Person");
//输出  静态代码块

这里可以看出Class.forName,跟进下查看调用关系

image-20220322221106974

这里我们可看到进入函数之后又调用了forName0函数,找到forName0函数

image-20220322221530971

可以发现这是由native修饰的,参数里有个initialize,应该是判断是否初始化

native:一个native方法就是一个Java调用非Java代码的接口。一个native方法是指该方法的实现由非Java语言实现,比如用C或C++实现。

然后在函数列表中还可以发现有一个传三个参数的forName函数

image-20220322221852530

传入类名,是否初始化布尔值以及加载类,当initialize传入false时,没有输出

什么是ClassLoader

类加载分析

ClassLoader cl = ClassLoader.getSystemClassLoader();
System.out.println(cl);
//sun.misc.Launcher$AppClassLoader@18b4aac2

这里可以看到ClassLoader.getSystemClassLoader()实际上获取的是AppClassLoader

进行断点调试、分析

image-20220322222547972

然后课可以看到首先进入的是ClassLoader

image-20220322222925915

调用的是AppClassLoaderloadClass方法,然后继续向下跟进

image-20220322223045030

中间是一些安全检查的代码,走到后面调用父类的loadClass

image-20220322223121041

跟进然后又回到了ClassLoader,继续往下走,调用了`findLoadedClass,看这个类是否加载过,这里属于是双亲委派的流程,询问Extension ClassLoaderBootstrap ClassLoader是否加载过,如果没有加载就加载

image-20220322224221940

image-20211118173450479

走到下面判断parant(父属性),然后parant非空,存在ExtClassLoader,调用ExtClassLoaderloadClass,继续跟进

由于ExtClassLoader没有loadClass方法所以又回到了ClassLoader

image-20220322224551891

继续往下走,parentnull

image-20220322223726525

然后就会去Bootstrap里寻找,因为这是个普通的类,不会用系统加载类去加载,所以还是没找到

image-20220322224951473

继续向下找

image-20220322225056225

走到findClass,由于ClassLoaderfindClass的方法是需要重写的一个方法,所以会走到子类findClass

image-20220322225207901

但是这原本按照流程来说这是应该走到ExtClassLoader里的findClass,但实际上却走到了URLClassLoaderfindClass

image-20220322225525159

这个的原因是ExtClassLoaderAppClassLoader都没有findClass方法,但是都继承了URLClassLoader

image-20220322225431026

image-20220322225442217

因为现在是在ExtClassLoader里,肯定是找不到普通类的

image-20220322230123613

继续往下走,然后退回到AppClassLoader里了,接着调用findClass

image-20220322230935910

又回到了URLClassLoader,继续跟进

image-20220322231057557

分析findClass

protected Class<?> findClass(final String name)
    throws ClassNotFoundException
{
    final Class<?> result;
    try {
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction<Class<?>>() {
                public Class<?> run() throws ClassNotFoundException {
                    String path = name.replace('.', '/').concat(".class");// 这里是获取类路径比如:Person.clss
                    Resource res = ucp.getResource(path, false); // ucp是一个URLClassPath类,在当前加载路径去找这个类
                    if (res != null) {
                        try {
                            return defineClass(name, res);
                        } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                        }
                    } else {
                        return null;
                    }
                }
            }, acc);
    } catch (java.security.PrivilegedActionException pae) {
        throw (ClassNotFoundException) pae.getException();
    }
    if (result == null) {
        throw new ClassNotFoundException(name);
    }
    return result;
}

在这里可以找到这个类

image-20220322231515365

最后调用了defineClass,实际上是在这里进行了类的加载,继续跟进,走到了URLClassLoaderdefindClass方法,中间都是一些检验的代码,最后又调用了一个defindClass

image-20220322231728288

走到了URLClassLoader的父类SecureClassLoader,又调用了一个defindClass,跟进

image-20220322231946256

然后又回到了ClassLoaderdefindClass,然后调用了defineClass1

image-20220322232231776

defineClass1是由native修饰的,是由c++写的,最后的类加载就是在这完成的,到这这个类的加载过程就结束了

image-20220322232452487

流程:

  • 继承关系
    • ClassLoader->SecureClassLoader->URLClassLoader->AppClassLoader
  • 调用
    • loadClass->findClass(重写的方法)->defineClass(从字节码加载类)

使用URLClassLoader进行任意类加载

知识点

  • 支持的协议
    • file
    • http
    • jar

测试

file

首先定义一个Hello类,进行编译,然后移动到别的目录,在把原本的类删了,进行测试

image-20220322235256712

Hello
public class Hello {
    static {
        System.out.println("Hello");
    }
}

成功进行加载

image-20220322235453506

calc
import java.io.IOException;

public class Test {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

尝试下弹出计算器

http

在文件路径起个http服务

成功加载

同时服务端也收到了请求

使用defindClass进行任意类加载

image-20220323001815010

成功加载类

image-20220323001919826

使用Unsafe进行任意类加载

Unsafe是java底层提供的一个类,Unsafe类主要用于执行非常底层、不安全操作的方法,例如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源调度能力方面起到了很大的作用。

Unsafe中也有一个defineClass

image-20220323003216041

虽然这个是一个public方法,但是并不能直接使用,因为Unsafe类的构造被私有化了,Unsafe类对外只提供一个静态方法来获取当前Unsafe实例,我们要获取theUnsafe对象

成功加载类

image-20220323003249464

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇