1. 自定义类加载器的流程

  • 继承 java.lang.ClassLoader
  • 首先检查请求的类型是否已经被类加载器加载到内存中了,如果已经加载,直接返回
  • 委派给父类加载器加载,如果父类加载器能够完成,则返回父类加载器返回的Class实例
  • 调用本类加载器的findClass(...)方法,试图获得对应的字节码,如果获取得到,则调用defineClass(...)方法导入类型到方法区,;如果获取不到字节码,或者其他原因失败了,返回异常给loadClass(...),loadClass(...)转抛异常,终止加载过程.

被两个类加载器加载的类,JVM认为是不同的类.

2. 加载器的种类

  • 网络类加载器
  • 文件类加载器
  • 加密解密类加载器(取反操作,DES对称加密解密)

3. 重写findClass和loadClass的区别

  • 重写loadClass方法(双亲委派机制) - 如果要想在JVM的不同类加载器中保留具有相同全限定名的类,那就要通过重写loadClass来实现,此时首先是通过用户自定义的类加载器来判断该类是否可加载,如果可以加载就由自定义的类加载器进行加载,如果不能够加载才交给父类加载器去加载。这种情况下,就有可能有大量相同的类,被不同的自定义类加载器加载到JVM中,并且这种实现方式是不符合双亲委派模型。但是不能够说这种实现方式就一定是错误的,有可能当前的场景就需要这样的方式,如容器插件应用场景就适合。
  • 重写findClass方法(代理机制)
    重写findClass方法的自定义类,首先会通过父类加载器进行加载,如果所有父类加载器都无法加载,再通过用户自定义的findClass方法进行加载。如果父类加载器可以加载这个类或者当前类已经存在于某个父类的容器中了,这个类是不会再次被加载的,此时用户自定义的findClass方法就不会被执行了。重写findClass方法是符合双亲委派模式的,它保证了相同全限定名的类是不会被重复加载到JVM中。

4. 实例

4.1. 文件类加载器

//FileClassLoader
package com.jiaruigang.util;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

@SuppressWarnings ("all")
public class FileClassLoader extends ClassLoader{
	private String rootDir;

	public FileClassLoader(String dir){
		this.rootDir = dir;
	}

	@Override
	public Class<?> findClass(String name) throws ClassNotFoundException {
		//查看是否已经加载过
		Class<?> c = findClass (name);

		if( c == null){
			//让父类加载器加载
			ClassLoader loader = this.getParent ();
			c = loader.loadClass (name);
			if ( c == null ){
				//自己加载
				byte[] classData = new byte[0];
				try {
					classData = getClassData(name);
				} catch (IOException e) {
					System.out.println ("加载失败");
				}
				if (classData == null){
					throw new ClassNotFoundException ();
				} else {
					c = defineClass (name,classData, 0, classData.length);
				}
			}
		}
		return c;
	}

	/**
	 *
	 * @param name 类的全名
	 * @return 类的字节数组
	 */
	private byte[] getClassData(String name) throws IOException {
		String path = name.replaceAll ("\\.","/");
		return Files.readAllBytes (Paths.get (this.rootDir + "/" + path + ".class"));
	}

}

Hello

//Hello类
package com.jiaruigang.util;

public class Hello{
	public static void main(String[] args) {
		System.out.println ("");
	}
	public void print(){
		System.out.println ("加载到了.");
	}
}
TestLoader

//测试类
package com.jiaruigang;

import com.jiaruigang.util.*;

import java.lang.reflect.InvocationTargetException;

public class TestLoader {
	public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
		FileClassLoader loader = new FileClassLoader (
				"G:/test");

		Class<?> c = loader.loadClass ("com.jiaruigang.util.Hello");

		Hello hello = (Hello) c.getDeclaredConstructor ().newInstance ();

		hello.print ();
	}
}

4.2. 网络类加载器

package com.jiaruigang.util;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

public class NetClassLoader extends ClassLoader{
	private String rootUrl;

	public NetClassLoader(String url){
		this.rootUrl = url;
	}
	@Override
	public Class<?> findClass(String name) throws ClassNotFoundException{
		Class<?> c = findClass (name);
		if (c == null){
			ClassLoader loader = this.getParent ();
			c = loader.loadClass (name);
			if (c == null){
				byte[] bytes = new byte[0];
				try {
					bytes = getClassData(name);
				} catch (IOException e) {
					e.printStackTrace ();
				}
				c = defineClass (name,bytes,0,bytes.length);
				if (c == null){
					throw new ClassNotFoundException ();
				}
			}
		}

		return c;
	}

	private byte[] getClassData(String name) throws IOException {
		String path = rootUrl + "/" + name.replace ('.','/') + ".class";

		InputStream in = new URL (path).openStream ();
		return in.readAllBytes ();
	}
}