自定义类加载器
我们如果需要自定义类加载器,只需要继承ClassLoader类,并覆盖掉findClass方法即可。
自定义文件类加载器
import java.io.*; /** * @author WGR * @create 2020/4/26 -- 21:57 */ public class MyFileClassLoader extends ClassLoader { private String directory;//被加载的类所在的目录 /** * 指定要加载的类所在的文件目录 * @param directory */ public MyFileClassLoader(String directory,ClassLoader parent){ super(parent); this.directory = directory; } public MyFileClassLoader(String directory) { this.directory = directory; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { //把类名转换为目录 String file = directory+ File.separator+name.replace(".", File.separator)+".class"; //构建输入流 InputStream in = new FileInputStream(file); //存放读取到的字节数据 ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte buf[] = new byte[1024]; int len = -1; while((len=in.read(buf))!=-1){ baos.write(buf,0,len); } byte data[] = baos.toByteArray(); in.close(); baos.close(); return defineClass(name,data,0,data.length); } catch (IOException e) { throw new RuntimeException(e); } } public static void main(String[] args) throws Exception { MyFileClassLoader myFileClassLoader = new MyFileClassLoader("d:/"); Class clazz = myFileClassLoader.loadClass("com.topcheer.Demo"); clazz.newInstance(); } }
import java.io.ByteArrayOutputStream; import java.io.File; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; public class MyURLClassLoader extends ClassLoader { private String url; public MyURLClassLoader(String url) { this.url = url; } protected Class<?> findClass(String name) throws ClassNotFoundException { try { String path = url+ "/"+name.replace(".","/")+".class"; URL url = new URL(path); InputStream inputStream = url.openStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int len = -1; byte buf[] = new byte[1024]; while((len=inputStream.read(buf))!=-1){ baos.write(buf,0,len); } byte[] data = baos.toByteArray(); inputStream.close(); baos.close(); return defineClass(name,data,0,data.length); } catch (Exception e) { e.printStackTrace(); } return null; } public static void main(String[] args) throws Exception{ MyURLClassLoader classLoader = new MyURLClassLoader("http://localhost:8080/examples"); Class clazz = classLoader.loadClass("com.topcheer.Demo"); clazz.newInstance(); } }
热部署类加载器
当我们调用loadClass方法加载类时,会采用双亲委派模式,即如果类已经被加载,就从缓存中获取,不会重新加载。如果同一个class被同一个类加载器多次加载,则会报错。因此,我们要实现热部署让同一个class文件被不同的类加载器重复加载即可。但是不能调用loadClass方法,而应该调用findClass方法,避开双亲委托模式,从而实现同一个类被多次加载,实现热部署。
public static void main(String[] args) throws Exception { MyFileClassLoader myFileClassLoader1 = new MyFileClassLoader("d:/"); MyFileClassLoader myFileClassLoader2 = new MyFileClassLoader("d:/",myFileClassLoader1); Class clazz1 = myFileClassLoader1.loadClass("com.topcheer.Demo"); Class clazz2 = myFileClassLoader2.loadClass("com.topcheer.Demo"); System.out.println("class1:"+clazz1.hashCode()); System.out.println("class2:"+clazz2.hashCode()); MyFileClassLoader myFileClassLoader3 = new MyFileClassLoader("d:/"); MyFileClassLoader myFileClassLoader4 = new MyFileClassLoader("d:/",myFileClassLoader3); Class clazz3 = myFileClassLoader3.findClass("com.topcheer.Demo"); Class clazz4 = myFileClassLoader4.findClass("com.topcheer.Demo"); System.out.println("class3:"+clazz3.hashCode()); System.out.println("class4:"+clazz4.hashCode()); }
类的显式与隐式加载
类的加载方式是指虚拟机将class文件加载到内存的方式。
显式加载是指在java代码中通过调用ClassLoader加载class对象,比如Class.forName(String name);this.getClass().getClassLoader().loadClass()加载类。
隐式加载指不需要在java代码中明确调用加载的代码,而是通过虚拟机自动加载到内存中。比如在加载某个class时,该class引用了另外一个类的对象,那么这个对象的字节码文件就会被虚拟机自动加载到内存中。
线程上下文类加载器
在Java中存在着很多的服务提供者接口SPI,全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,这些接口一般由第三方提供实现,常见的SPI有JDBC、JNDI等。这些SPI的接口(比如JDBC中的java.sql.Driver)属于核心类库,一般存在rt.jar包中,由根类加载器加载。而第三方实现的代码一般作为依赖jar包存放在classpath路径下,由于SPI接口中的代码需要加载具体的第三方实现类并调用其相关方法,SPI的接口类是由根类加载器加载的,Bootstrap类加载器无法直接加载位于classpath下的具体实现类。由于双亲委派模式的存在,Bootstrap类加载器也无法反向委托AppClassLoader加载SPI的具体实现类。在这种情况下,java提供了线程上下文类加载器用于解决以上问题。
线程上下文类加载器可以通过java.lang.Thread的getContextClassLoader()来获取,或者通过setContextClassLoader(ClassLoader cl)来设置线程的上下文类加载器。如果没有手动设置上下文类加载器,线程将继承其父线程的上下文类加载器,初始线程的上下文类加载器是系统类加载器(AppClassLoader),在线程中运行的代码可以通过此类加载器来加载类或资源。
显然这种加载类的方式破坏了双亲委托模型,但它使得java类加载器变得更加灵活。
我们以JDBC中的类为例做一下说明。在JDBC中有一个类java.sql.DriverManager,它是rt.jar中的类,用来注册实现了java.sql.Driver接口的驱动类,而java.sql.Driver的实现类一般都是位于数据库的驱动jar包中的。
会去加载于jar包中的META-INF/services中的java.sql.Driver,并加载文件中的指定实现类