前言
Java程序是由class文件组成的一个完整的应用程序。在程序运行时,并不会一次性加载所有的class文件进入内存,而是通过Java的类加载机制进行动态加载,从而转换成java.lang.Class
类的一个实例。
ClassLoader类
ClassLoader是一个抽象类,主要功能是通过指定的类的名称,找到或生成对应的字节码,返回一个java.lang.Class
类的实例。开发者可以继承ClassLoader类来实现自定义的类加载器。
方法 | 说明 |
---|---|
getParent() | 返回该类加载器的父类加载器 |
loadClass(String name) | 加载名称为name的类,返回的结果是java.lang.Class 类的实例 |
findClass(String name) | 查找名称为name的类,返回的结果是java.lang.Class 类的实例 |
findLoadedClass(String name) | 查找名称为name的已经被加载过的类,返回的结果是java.lang.Class 类的实例 |
defineClass(String name,byte[] b,int off,int len) | 把字节数组b中的内容转换成Java类,返回的结果是java.lang.Class 类的实例,该方法被声明为final |
resolveClass(Class<?> c) | 链接指定的Java类 |
loadClass()方法的流程
当loadClass()方法被调用时,会首先使用findLoadedClass()
方法判断该类是否已经被加载,如果未被加载,则优先使用加载器的父类加载器进行加载。当不存在父类加载器,无法对该类进行加载时,则会调用自身的findClass()
方法,因此可以重写findClass()
方法来完成一些类加载的特殊要求。
protected Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException
{
synchronized(getClassLoadingLock(name)){
Class<?> c = findLoadedClass(name);
if(c == null){
long t0 = System.nanoTime();
try{
if(parent != null){
c = parent.loadClass(name,false);
}else{
c = findBootstrapClassOrNull(name);
}
}catch(ClassNotFoundException e){
//省略
}
if (c == null){
//省略
c = findClass(name);
//省略
}
}
if (resolve){
resolveClass(c);
}
return c;
}
}
自定义的类加载器
根据loadClass()方法的流程,可以发现通过重写findClass()方法,利用defineClass()方法来将字节码转换成java.lang.Class
类对象,就可以实现自定义的类加载器。
hello.class
package com.atguigu.java;
public class hello {
public void sayHello(){
System.out.println("hello~");
}
}
DemoClassLoader.class
package com.atguigu.java;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class DemoClassLoader extends ClassLoader{
private byte[] bytes;
private String name = "";
public DemoClassLoader(String name, byte[] bytes) {
this.name = name;
this.bytes = bytes;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException{
if(name.equals(this.name)){
defineClass(name,bytes,0,bytes.length);
}
return super.findClass(name);
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
String clzzName = "com.atguigu.java.hello";
byte[] testBytes = new byte[]{
-54,-2,-70,-66,0,0,0,52,0,28,10,0,6,0,14,9,
0,15,0,16,8,0,17,10,0,18,0,19,7,
0,20,7,0,21,1,0,6,60,105,110,105,116,62,1,0,
3,40,
};
DemoClassLoader demo = new DemoClassLoader(clzzName,testBytes);
Class clazz = demo.loadClass(clzzName);
Constructor constructor = clazz.getConstructor();
Object obj = constructor.newInstance();
Method method = clazz.getMethod("sayHello");
method.invoke(obj);
}
}
loadClass()方法与Class.forName的区别
loadClass()方法只对类进行加载,不会对类进行初始化。Class.forName会默认对类进行初始化。当对类进行初始化时,静态的代码块就会得到执行,而代码块和构造函数则需要适合的类实例化才能得到执行
Dog.class
package com.atguigu.java;
public class Dog {
static {
System.out.println("静态代码块执行");
}
{
System.out.println("代码块执行");
}
public Dog() {
System.out.println("构造方法执行");
}
}
ClassLoaderTest.class
package com.atguigu.java;
public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException{
Class.forName("com.atguigu.java.Dog");
ClassLoader.getSystemClassLoader().loadClass("com.atguigu.java.Dog");
}
}
URLClassLoader
URLClassLoader类是ClassLoader的一个实现,拥有从远程服务器上加载类的能力。通过URLClassLoader可以实现对一些WebShell的远程加载、对某个漏洞的深入利用。