• 自定义类加载器


     自定义类加载器

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

     

  • 相关阅读:
    tomcat虚拟目录配置
    关于JS闭包
    数据列表里结合负边距做间隔线的技巧需注意的小细节
    前端优化技巧笔记
    浏览器工作原理相关笔记
    工作小心得(关于绝对定位元素)
    关于定位和溢出处理的一些小经历
    关于定位和z-index的一些小经历
    fullpage实现(-)
    移动端布局-实例
  • 原文地址:https://www.cnblogs.com/dalianpai/p/12782853.html
Copyright © 2020-2023  润新知