java(web安全)--Classloader

什么是ClassLoader?

我们知道Java程序在Java虚拟机(JVM)上运行。当我们编译Java类时,JVM将创建字节码,该字节码与平台和机器无关。字节码存储在.class文件中。当我们尝试使用一个类时,ClassLoader将其加载到内存中。

内置的ClassLoader类型

Java内置了三种类型的内置ClassLoader。

1.Bootstrap Class Loader 加载JDK内部类。它加载rt.jar和其他核心类,例如java.lang。*包类。
2.Extensions Class Loader 它从JDK扩展目录(通常为$ JAVA_HOME / lib / ext目录)中加载类。
3.System Class Loader该类加载器从当前类路径加载类。我们可以在使用-cp或-classpath命令行选项调用程序时设置classpath。

 ClassLoader层次结构

ClassLoader在将类加载到内存中是分层的。每当提出加载类的请求时,它都会将其委托给父类加载器。这就是在运行时环境中保持唯一性的方式。如果父类加载器找不到该类,则该类加载器本身将尝试加载该类。
让我们通过执行以下java程序来了解这一点。
public class ClassLoaderTest {

    public static void main(String[] args) {

        System.out.println("class loader for HashMap: "
                + java.util.HashMap.class.getClassLoader());
        System.out.println("class loader for DNSNameService: "
                + sun.net.spi.nameservice.dns.DNSNameService.class
                .getClassLoader());
        System.out.println("class loader for this class: "
                + ClassLoaderTest.class.getClassLoader());

    }

}

 

Java ClassLoader如何工作?

让我们从上面的程序输出中了解类加载器的工作方式。

该java.util.HashMap中的ClassLoader是来为null,这反映了引导类加载器。DNSNameService类ClassLoader是ExtClassLoader。由于类本身位于CLASSPATH中,因此System ClassLoader会加载它。
  • 当我们尝试加载HashMap时,我们的System ClassLoader将其委托给Extension ClassLoader。扩展类加载器将其委托给Bootstrap ClassLoader。引导类加载器会找到HashMap类并将其加载到JVM内存中。
  • DNSNameService类遵循相同的过程 但是,Bootstrap ClassLoader无法找到它,因为它位于$ JAVA_HOME / lib / ext / dnsns.jar中。因此,它由扩展类加载器加载。

 为什么要用Java编写自定义ClassLoader?

Java默认的ClassLoader可以从本地文件系统加载类,这在大多数情况下已经足够了。但是,如果在加载类时希望在运行时或从FTP服务器或通过第三方Web服务获取类,则必须扩展现有的类加载器。例如,AppletViewers从远程Web服务器加载类。
当JVM请求一个类时,它loadClass()通过传递类的完全分类名称来调用ClassLoader的功能。
loadClass()函数调用该findLoadedClass()方法以检查是否已加载该类。需要避免多次加载相同的类。
如果尚未加载该类,则它将把请求委派给父ClassLoader以加载该类。
如果父类ClassLoader找不到该类,则它将调用findClass()方法在文件系统中查找这些类。

Java自定义ClassLoader示例

我们将通过扩展ClassLoader类并覆盖loadClass(String name)方法来创建自己的ClassLoader。

如果类名将从com.journaldev开始,那么我们将使用自定义类加载器加载它,否则我们将调用父ClassLoader loadClass()方法加载该类。

 1. CCLoader.java

private byte[] loadClassFileData(String name):此方法将从文件系统读取类文件到字节数组。
private Class<?> getClass(String name):此方法将调用loadClassFileData()函数,并且通过调用父defineClass()方法,它将生成Class并将其返回。
public Class<?> loadClass(String name):此方法负责加载类。如果类名以com.journaldev(我们的示例类)开头,则它将使用getClass()方法加载它,否则它将调用父loadClass()函数来加载它。
public CCLoader(ClassLoader parent):这是构造函数,负责设置父ClassLoader。


import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;


public class CCLoader extends ClassLoader {
    public CCLoader(ClassLoader parent) {
        super(parent);
    }
    private Class getClass(String name) throws ClassNotFoundException{
        String file=name.replace('.', File.separatorChar)+".class";
        byte[] b= null;
        final Base64.Decoder decoder = Base64.getDecoder();
        final Base64.Encoder encoder = Base64.getEncoder();
        
        try {
            b=loadClassFileData(file);
            S
            final String encodedText = encoder.encodeToString(b);
            System.out.println(encodedText);
            Class c=defineClass(name,b,0,b.length);
            resolveClass(c);
            String cc=c.toString();
            System.out.println("thisbyte +"+cc);
            return c;
            
            }
        catch(IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    public Class loadClass(String name) throws ClassNotFoundException{
        System.out.println("Loading Class '"+name+"'");
        if(name.startsWith("com.loaderTest")) {
            System.out.println("Loading Class use CCloader");
            return getClass(name);
        }
        return super.loadClass(name);
    }
    private byte[] loadClassFileData(String name) throws IOException{
        InputStream stream=getClass().getClassLoader().getResourceAsStream(name);
        int size=stream.available();
        byte buff[]=new byte[size];
        DataInputStream in=new DataInputStream(stream);
        in.readFully(buff);
        in.close();
        return buff;
        
    }
}

2. CCRun.java

这是带有主要功能的测试类我们正在创建ClassLoader的实例,并使用其loadClass()方法加载示例类。

加载该类之后,我们将使用Java Reflection API来调用其方法。

package Test;

import java.lang.reflect.Method;

public class CCRun {

    public static void main(String args[]) throws  Exception{
        String progClass=args[0];
        String progArgs[]=new String[args.length-1];
        System.arraycopy(args, 1, progArgs, 0, progArgs.length);
        CCLoader ccl=new CCLoader(CCRun.class.getClassLoader());
        Class clas =ccl.loadClass(progClass);
        Class mainArgType[]={(new String[0]).getClass()};
        Method main=clas.getMethod("main", mainArgType);
        Object argsArray[]= {progArgs};
        main.invoke(null, argsArray);
        Method printCL=clas.getMethod("printCl", null);
        printCL.invoke(null, new Object[0]);
    }
}

3. Foo.java和Bar.java

这些是由我们的自定义类加载器加载的测试类。他们有一个printCL()方法,该方法将被调用以打印ClassLoader信息。

Foo类将由我们的自定义类加载器加载。Foo使用Bar类,因此Bar类也将由我们的自定义类加载器加载。

 FOO.java

package com.loaderTest;

public class Foo {
    static public void main(String args[]) throws Exception{
        System.out.println("传入的参数:"+args[0]+args[1]);
        Bar bar=new Bar(args[0],args[1]);
        bar.printCL();
        
    }
    public static void printCL() throws Exception{
        System.out.println("Foo ClassLoader: "+Foo.class.getClassLoader());
        Runtime.getRuntime().exec("cmd.exe /c notepad.exe");
    }

        
    }

 Bar.java

package com.loaderTest;

public class Bar {
    public Bar(String a,String b) {
        System.out.println("Bar args"+a+b);
    }
    public void printCL() throws Exception {
        System.out.println("Bar Classloader:"+Bar.class.getClassLoader());
        Runtime.getRuntime().exec("cmd.exe /c notepad.exe");
    }

}

 

C:Usersjavaeclipse-workspaceClassLoadersrc>javac -cp . com/loaderTest/Foo.j
ava

C:Usersjavaeclipse-workspaceClassLoadersrc>javac -cp . com/loaderTest/Bar.j
ava

C:Usersjavaeclipse-workspaceClassLoadersrc>javac CCLoader.java

C:Usersjavaeclipse-workspaceClassLoadersrc>javac CCRun.java
CCRun.java:19: 警告: 最后一个参数使用了不准确的变量类型的 varargs 方法的非 varar
gs 调用;
        Method printCL = clas.getMethod("printCL", null);
                                                   ^
  对于 varargs 调用, 应使用 Class
  对于非 varargs 调用, 应使用 Class[], 这样也可以抑制此警告
注: CCRun.java使用了未经检查或不安全的操作。
注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。
1 个警告

这里我们 print的是loadClassFileData的结果,也就是加载资源文件,返回byte给getclass方法

参考

https://www.journaldev.com/349/java-classloader#why-write-a-custom-classloader-in-java
https://www.cnblogs.com/alter888/p/9140732.html
原文地址:https://www.cnblogs.com/-zhong/p/14286591.html