自定义类加载器

 自定义类加载器

我们如果需要自定义类加载器,只需要继承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,并加载文件中的指定实现类

 

posted @ 2020-04-26 22:29  天宇轩-王  阅读(358)  评论(0编辑  收藏  举报