在java中的每一个类都会对应一个Class对象,我们通常把这个Class对象称之为字节码对象,那么这个字节码对象是由谁来产生的呢?java中的类是由谁来加载进内存的呢?接下来我介绍的就是负责将java中的字节码文件加载到内存,创建Class对象的类ClassLoader,也就是java中的类加载器。
类加载器一般由系统来提供,不需要我们自己实现,但是通过我们自定义的类加载器可以更加灵活的加载class文件。在java中有三个默认的类加载器分别是Bootstrap ClassLoader(启动类加载器)、Extension ClassLoader(扩展类加载器)、Application ClassLoader(应用程序类加载器),其中启动类加载器负责加载java的基础类,扩展类加载器负责加载java的一下扩展类,应用程序类加载器负责加载应用程序的类也就是在类路径中指定的类。这三个类加载器有父子关系,其中Application ClassLoader的父类是Extension ClassLoader,Extension ClassLoader的父类是Bootstrap ClassLoader。但是它们不是父子的继承关系而是父子的委派关系,在Application ClassLoader中有一个parent变量指向Extension ClassLoader,在Extension ClassLoader中也有一个parent变量指向Bootstrap ClassLoader。一个类在被加载时首先会判断这个类是否被加载过,如果这个类已经被加载过了就会返回Class对象,如果没有被加载过会先让父加载器加载如果父加载器加载成功直接返回Class对象,如果父加载器没有加载成功才自己尝试加载。这个类加载的过程一般我们称之为"双亲委派"模式。java之所以采用这种方式加载类为了防止类的重复加载,防止用户自定义的类型覆盖java原有的类型。
了解了java中的类加载机制后我们要介绍今天的重点类:ClassLoader。类ClassLoader是一个抽象类,每一个Class对象都有一个getClassLoader()方法来获取ClassLoader。ClassLoaer类中有一个getParent()方法来获取这个ClassLoader的父加载器。
public class TestClassLoader { public static void main(String[] args) { ClassLoader classLoader = TestClassLoader.class.getClassLoader(); while(classLoader!=null){ System.out.println(classLoader.getClass().getName()); classLoader = classLoader.getParent(); } System.out.println(String.class.getClassLoader()); } } 输出: sun.misc.Launcher$AppClassLoader sun.misc.Launcher$ExtClassLoader null
这里需要说明的是Bootstrap ClassLoader是用C++写的所以最后一个输出返回了null。在ClassLoader中还有一个静态的方法返回的是默认的系统类加载器 getSystemClassLoader。ClassLoader中还有一个重要的方法loadClass(String name)用来加载类。
public class TestClassLoader { public static void main(String[] args) throws ClassNotFoundException { ClassLoader classLoader = ClassLoader.getSystemClassLoader(); Class cls = classLoader.loadClass("com.reflect.generic.TestClassLoader"); System.out.println(cls.getClassLoader().getClass().getName()); } }
这里需要说明一下,在学习反射的时候我学习到使用Class类的静态方法forName也可以加载类,今天我们介绍的ClassLoader的loadClass方法也可以加载类,但是他们两个是有区别的,使用Class的forName方法加载类会初始化类但是使用loadClass方法不会初始化类。
java的类加载机制是允许用户自定义类加载器的。自定义的类加载器可以实现很多强大的功能比如热部署技术和web不用应用的隔离。在java中要自定义类加载器并没有想象中的那么困难。我们只需要继承ClassLoader类然后重写里面的findClass方法即可。findClass中我们主要是写如何通过自己的逻辑找到class文件的字节形式然后调用defineClass(String name,byte[] b,int off,int len)其中name是类名,b是存放字节码的字节数组,off是起始位置,len是长度。
public class MyClassLoader extends ClassLoader { private String path; public MyClassLoader(String path){ this.path = path; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] b = getClassByte(name,this.path); return defineClass(name, b, 0, b.length); } private static byte[] getClassByte(String name,String path){ String classPath = name.replace(".","/"); String fileName = path+classPath + ".class"; BufferedInputStream ins = null; ByteArrayOutputStream os =null; try { ins = new BufferedInputStream(new FileInputStream(new File(fileName))); os = new ByteArrayOutputStream(); int i =0; while((i=ins.read())!=-1){ os.write(i); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { if(ins!=null){ try { ins.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(os!=null){ try { os.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return os.toByteArray(); } public String getPath() { return path; } public void setPath(String path) { this.path = path; } }
在调用的时候:
public class TestClassLoader { public static void main(String[] args) throws ClassNotFoundException { MyClassLoader myclassLoader = new MyClassLoader("E:\\react\\testclassloader\\"); Class cls = myclassLoader.findClass("com.reflect.generic.User"); System.out.println(cls.getClassLoader().getClass().getName()); } } 输出: com.reflect.generic.MyClassLoader
这里需要说明一下,不同的类加载器加载同一个类获取的Class对象是不同的。对于同一个类的每个ClassLoader只能加载一次。
原文地址:https://www.cnblogs.com/suyang-java/p/10987891.html