为什么要自定义ClassLoader进行类加载

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么要自定义ClassLoader进行类加载相关的知识,希望对你有一定的参考价值。

假如,你开发的java程序都需要从E:classloader1目录下的类文件中加载class,而不是系统指定的系统目录或者classpath目录下加载,则如何解决? 需要自定义classloader。

ClassLoader:加载各种class文件到JVM中。ClassLoader是抽象类。

类的加载过程分为加载阶段、连接阶段、初始化。

  1. 加载阶段:寻找class文件。
  2. 连接阶段:验证class文件的正确性;为类的静态变量准备内存,并为其初始化默认值;把类中符号引用转换为直接引用。
  3. 初始化阶段:为累的静态变量赋予正确的初始值,也就是在代码的值。

JVM目前对类的初始化时一次lazy。所以何时初始化就会根据不同情况而不同,如果主动使用,则会进行类的初始化。

类的6种主动使用情况:

  1. 通过调用静态变量会导致类的初始化。 但是若是静态常量,则不会。
  2. 调用静态方法,会导致类的初始化过程。
  3. 调用class.forName。
  4. 调用子类会导致父类初始化,但是如果只是通过子类调用父内的静态方法后者变量,则不会导致子类初始化。
  5. 调用main方法的类也会被初始化。
  6. 调用 new关键字,必然会进行类的初始化,但是数组除外。

类的被动使用:除了主动的以外,都是被动的。

类的加载过程中阶段,常量是在javac编译阶段就替换了值,所以不会再类的连接阶段进行默认初始化(给相应类变量一个相关类型在没有被设置时候的默认值)。

类的初始化阶段中,赋值动作和静态语句块的执行代码,这个是按照顺序执行。虚拟机会保证这些动作会优先执行,因此父类的静态变量总是能够得到优先赋值。

JVM默认的加载类去寻找特定的几个目录下的类进行加载。比如当前路径,classpath等。若想自定义路径,或者在加载过程中,有些个性化的需求,则需要自定义加载类进行加载。
自定义加载类,还有一个好处,可以根据需求,进行单独卸载和重新加载,这样就可以进行无需停机的热部署。

下面时自定义加载类的使用方法:

  1. 先编写一个简单的类,用于测试被自定义类加载。
    ‘‘‘
    package classLoaderStudy;
    public class ClassLoaderHello {
    static
    {
    System.out.println("hello world");
    }
    public String welcome()
    {
    return "Hello world";
    }

}
‘‘‘

  1. 编写自定义加载类,

其中findClass是必须重写的,功能就是读入class文件位字节流,然后调用ClassLoader的自带方法defineClass方法对类在方法区进行定义,这样,才能使用这个自定义类。
‘‘‘
package classLoaderStudy;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class MyClassLoader extends ClassLoader{

   private final static Path DEFAULT_CLASS_DIR = Paths.get("E:",  "classloader1");

   private final Path classDir;

   public MyClassLoader()
   {
          super();
          this.classDir = DEFAULT_CLASS_DIR;

   }
   public MyClassLoader(String classDir)
   {
          super();
          this.classDir = Paths.get(classDir);
   }

   public MyClassLoader(String classDir,ClassLoader parent)
   {
          super(parent);
          this.classDir = Paths.get(classDir);
   }

   private byte[] readClassBytes(String name)
   throws ClassNotFoundException
   {
          String classPath = name.replace(".", "/");
          Path classFullPath =  classDir.resolve(Paths.get(classPath+".class"));
          if(!classFullPath.toFile().exists())
          {
                 throw new ClassNotFoundException("The Class "+name+" not  found");
          }
          try(ByteArrayOutputStream baos = new ByteArrayOutputStream())
          {
                 Files.copy(classFullPath, baos);
                 return baos.toByteArray();
          }
          catch(IOException e)
          {
                 throw new ClassNotFoundException("Load the class "+  name +"  occur error.",e);
          }
   }

   @Override
   /*
    * (non-Javadoc)
    * @see java.lang.ClassLoader#findClass(java.lang.String)
    * 必须要重写这个类
    */
   protected Class<?> findClass(String name) throws ClassNotFoundException
   {
          byte[] classBytes = this.readClassBytes(name);
          if(null == classBytes || classBytes.length == 0)
          {
                 throw new ClassNotFoundException("can not load the class ");
          }
          return this.defineClass(name, classBytes, 0,classBytes.length);
   }

   @Override
   public String toString()
   {
          return "My ClassLoader";
   }
   public static void main(String[] args) {
          MyClassLoader classLoader = new MyClassLoader();
          Class<?> aClass  =null;
          try {
              aClass =  classLoader.loadClass("classLoaderStudy.ClassLoaderHello");
          } catch (ClassNotFoundException e) {
                 e.printStackTrace();
          }
          System.out.println(aClass.getClassLoader());
          try {
                 Object helloWorld = aClass.newInstance();
                 System.out.println(helloWorld);
                 Method welcomeMethod = aClass.getMethod("welcome");
                 String result = (String) welcomeMethod.invoke(helloWorld);
                 System.out.println(result);

          } catch (InstantiationException e) {

                 e.printStackTrace();
          } catch (IllegalAccessException e) {

                 e.printStackTrace();
          }catch (Exception e)
          {
                 e.printStackTrace();
          }

   }

}
‘‘‘

  1. 测试

classLoaderStudy\ClassLoaderHello.class文件拷贝到 E:\classloader1目录下,
E:\classloader1\classLoaderStudy\ClassLoaderHello.class并将工程中的 ClassLoaderHello.java文件和ClassLoaderHello.class文件删除,从而确保不会加载工程中的这个类,以便测试自定义加载类。
在Eclipse中运行MyClassLoader类,结果如下
‘‘‘
My ClassLoader
hello worldbr/>[email protected]
Hello world
‘‘‘


以上是关于为什么要自定义ClassLoader进行类加载的主要内容,如果未能解决你的问题,请参考以下文章

java 自定义类的加载器

自定义一个类加载器

手写一个ClassLoader类加载器

类的加载器 ClassLoader

ClassLoader类的原理

JVM:如何实现一个自定义类加载器?