加入收藏 | 设为首页 | 会员中心 | 我要投稿 北几岛 (https://www.beijidao.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

Java安全之 ClassLoader类加载器

发布时间:2021-05-21 07:14:34 所属栏目:大数据 来源: https://www.jb51.cc
导读:Java学习之 ClassLoader类加载器 0x00 前言 前面这里抛出一个问题,Java到底是什么类型的编程语言?是编译型?还是解释型?在这个问题是其实一直都都有疑惑,如果说是解释型语言的话,那么为什么需要编译呢?如果说是编译型语言的话,那么在编译完成后,需要

Java学习之 ClassLoader类加载器

0x00 前言

前面这里抛出一个问题,Java到底是什么类型的编程语言?是编译型?还是解释型?在这个问题是其实一直都都有疑惑,如果说是解释型语言的话,那么为什么需要编译呢?如果说是编译型语言的话,那么在编译完成后,需要JVM去解析才能运行呢?其实两种说法都对,也不对。下面来看看java的一个执行流程就知道是怎么回事了。

0x01 类加载机制

Java中的源码.java后缀文件会在运行前被编译成.class后缀文件,Java类初始化的时候会调用java.lang.ClassLoader加载字节码,.class文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的.class文件,并创建对应的class对象,将class文件加载到虚拟机的内存。

在其中其实包含了比较多的内容,下面来看看他的详细执行流程。

具体的实现分为三大步骤:

第一步 加载:

类加载指的是将class文件读入内存,并为之创建一个java.lang.Class对象,即程序中使用任何类时,系统都会为之建立一个java.lang.Class对象,系统中所有的类都是java.lang.Class的实例。
类的加载由类加载器完成,JVM提供的类加载器叫做系统类加载器,此外还可以通过继承ClassLoader基类来自定义类加载器。

第二步 连接:

连接阶段负责把类的二进制数据合并到JRE中

其又可分为如下三个阶段:

验证:确保加载的类信息符合JVM规范,无安全方面的问题。

准备:为类的静态Field分配内存,并设置初始值。

解析:将类的二进制数据中的符号引用替换成直接引用。

第三步 初始化:

类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化

双亲委托机制

当一个类加载器查找class和resource时,是通过“委托模式”进行的,它首先会判断这个class是不是已经加载成功,如果没有加载的话它并不是自己进行查找,而是先通过父加载器,然后递归下去,直到Bootstrap ClassLoader,如果Bootstrap classloader找到了,直接返回,如果没有找到,则一级一级返回,最后是由自身去查找这些对象;这种机制就叫做双亲委托。

其他的方式也可以去加载类的二进制数据:

1.从本地文件系统加载class文件。

2.从JAR包中加载class文件,如JAR包的数据库启驱动类。

3.通过网络加载class文件。

4.把一个Java源文件动态编译并执行加载。

0x02 ClassLoader类加载器

前面提到过编译成class字节码后的文件,会使用类加载器加载字节码。也就是说在java中所有的类都会通过加载器进行加载才能运行。在JVM类加载器中最顶层的是Bootstrap ClassLoader(引导类加载器)Extension ClassLoader(扩展类加载器)App ClassLoader(系统类加载器)AppClassLoader是默认的类加载器,如果类加载时我们不指定类加载器的情况下,默认会使用AppClassLoader加载类。

ClassLoader类 核心方法:

1.loadClass(String className),根据名字加载一个类。
2.defineClass(String name,byte[] b,int off,int len),将一个字节流定义为一个类。
3.findClass(String name),查找一个类。
4.findLoadedClass(String name),在已加载的类中,查找一个类。
5.resolveClass(链接指定的Java类)

0x03 自定义Classloader加载class文件

在ClassLoader中有四个很重要实用的方法loadClass()、findLoadedClass()、findClass()、defineClass(),可以用来创建属于自己的类的加载方式;比如我们需要动态加载一些东西,或者从C盘某个特定的文件夹加载一个class文件,又或者从网络上下载class主内容然后再进行加载等。分三步搞定:

1、编写一个类继承ClassLoader抽象类;

2、重写findClass()方法;

3、在findClass()方法中调用defineClass()方法即可实现自定义ClassLoader;

下面来编写一个test类

package com.test;

public class test {
    public String method(){
        return "hello,world";
    }

}

编写完成后使用javac进行编译为class字节码文件

javac .test.java

会发现多出一个class字节码文件,那么我们需要再对其转换为byte类型,方便后面使用类加载器进行加载执行。

import sun.misc.IoUtils;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

public class ulits {

    public static void main(String[] args) throws IOException {
        InputStream fis = new FileInputStream("test.class");
        byte[] bytes = IoUtils.readFully(fis,-1,false);
        System.out.println(Arrays.toString(bytes));

    }
}

自定义加载器类:

package com.test;

import java.lang.reflect.Method;

public class classloadertest extends ClassLoader{
    private static String testclassname= "com.test.test";
    //转换byte后的字节码
    private static byte[] classbytes= new byte[]{-54,-2,-70,-66,52,29,10,6,15,9,16,17,8,18,19,20,7,21,22,1,60,105,110,116,62,3,40,41,86,4,67,111,100,101,76,78,117,109,98,114,84,97,108,91,106,118,47,103,83,59,99,70,115,46,12,23,24,25,-23,-114,-75,-47,-122,-18,-108,-111,-76,-26,-124,-84,-27,-89,-101,26,27,28,13,79,121,80,112,33,5,2,42,-73,-79,11,37,-78,-74,14};


    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //只处理com.test.test类
        if (name.equals(testclassname)) {
          //将一个字节流定义为一个类。
            return defineClass(testclassname,classbytes,classbytes.length);
        }
        return super.findClass(name);
    }

    public static void main(String[] args) throws Exception {
        //创建加载器
        classloadertest classloadertest = new classloadertest();
        //使用我们自定义的类去加载testclassname
        Class aClass = classloadertest.loadClass(testclassname);
        //反射创建test类对象
        Object o = aClass.newInstance();
        //反射获取method方法
        Method method = o.getClass().getMethod("method");
        //反射去调用执行method方法
        String str = (String) method.invoke(o);
        System.out.println(str);


    }
}

可以看到我们class中的method方法执行了。

总结

1、java文件编译成class字节码文件
2、转换字节码文件为byte类型,目的方便存储,加载执行
3、自定义一个类加载器类,自定义处理流程(findClass),实现对象的调用。过程为创建对象,加载class,反射创建自定义class的对象,反射获取需要执行的方法,执行方法。

参考文章

https://javasec.org/javase/ClassLoader/
https://blog.csdn.net/javazejian/article/details/73413292
https://blog.csdn.net/CNAHYZ/article/details/82219210
https://blog.csdn.net/briblue/article/details/54973413
https://blog.csdn.net/xyang81/article/details/7292380

0x04 结尾

对于这篇文章,对于没了解过类加载器的来说还是比较吃力的,比如我。在文中有些地方写的也比较模糊,所以贴了几个不错的文章在上面,作为参考。

(编辑:北几岛)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读