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:\Users\java\eclipse-workspace\ClassLoader\src>javac -cp . com/loaderTest/Foo.j ava C:\Users\java\eclipse-workspace\ClassLoader\src>javac -cp . com/loaderTest/Bar.j ava C:\Users\java\eclipse-workspace\ClassLoader\src>javac CCLoader.java C:\Users\java\eclipse-workspace\ClassLoader\src>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