IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Hello, ClassLoader

    klion26发表于 2017-03-25 12:31:22
    love 0

    这里所说的 ClassLoader 均指 Java ClassLoader。

    什么是 ClassLoader

    Java 的 ClassLoader 是 JVM 中的一个核心组件,用于动态加载 Java class,分为系统 ClassLoader 和用户自定义 ClassLoader。wikipedia 对 Java ClassLoader 的定义如下:

    The Java Classloader is a part of the Java Runtime Environment that dynamically loads Java classes into the Java Virtual Machine. Usually classes are only loaded on demand. The Java run time system does not need to know about files and file systems because of classloaders.

    在 JVM 启动的时候,会启动三个系统级的 ClassLoader:

    1. Bootstrap ClassLoader, 用于加载 ${JAVA_HOME}/jre/lib 目录下的 class
    2. Extensions ClassLoader,用于加载 ${JAVA_HOME}/jre/lib/ext 目录下的 class
    3. System ClassLoader,用于加载 java.class.path 路径下的 class,java.class.path 即用户设定的 CLASSPATH

    当然,用户还可以根据需要定义自己的 ClassLoader,只需要继承 ClassLoader 类即可。

    ClassLoader 可以用来做什么

    ClassLoader 的核心工作是用于加载 class,那么除了系统提供的 ClassLoader 外,我们是否还需要自定义的 ClassLoader 呢?在什么情况下会需要自定义的 ClassLoader 呢?
    我们先看自定义的 ClassLoader 能提供哪些功能:

    • 模块化设计,根据不同的模块定义不同的 ClassLoader
    • 避免冲突,能够将类限制在 ClassLoader 范围内,防止同一个类出现在多个模块中导致的冲突
    • 支持多版本,同一个系统中支持同一个类的多个版本,而不会造成冲突
    • 从任意地方加载类文件(可以从指定的路径,甚至远程 URL 地址)
    • 可以对 class 文件进行加密(加载的时候再进行解密)
    • 运行时动态加载类文件(热更新)

    如果我们希望实现上面功能中的任何一种,都需要实现自定义的 ClassLoader。能实现上面的这些功能,实际上是因为每一个 Java class 在 JVM 中由 <classloader, classnamne=""> 唯一表示,其中 ClassLoader 是加载该 class 的 ClassLoader,ClassName 则是形如 org.apache.spark.streaming.StreamingContext 这样的字符串(其中 org.apache.spark.streaming 是 package 名,StreamignContext 是 class 名。

    如何在运行时动态加载 class

    说了这么多,接下来将用一个简单的例子来说明,如何在程序运行时动态的加载 class。

    动态加载 class 是什么以及为什么需要

    动态加载 class 能够在作业不停止提供服务的情况下,按需加载我们需要的 class。其中不停止提供服务是我们需要动态加载 class 的主要原因,有些服务起来之后,希望能够 7*24 小时一直提供服务,但又能够在运行的过程中进行升级,加载我们需要的 class,替换掉原来的逻辑。

    如何实现一个动态加载程序

    实现一个动态加载程序,首先需要实现一个自定义的 ClassLoader,按照我们的逻辑来加载 class。这里我们的自定义 ClassLoader 大致如下

    import java.util.*;
    import java.util.jar.*;
    import java.io.*;
    import java.lang.reflect.*;
    
    public class MyClassLoader extends ClassLoader {
     private String directory;
     private Hashtable classes = new Hashtable();
    
    public MyClassLoader(String dir) {
     super(MyClassLoader.class.getClassLoader());
     this.directory = dir;
     }
    
    public synchronized Class loadClass(String name) throws ClassNotFoundException{
     return findClass(name);
     }
    
    public Class findClass(String className) {
     byte classByte[];
     Class result = null;
    
    result = (Class) classes.get(className);
     if (result != null) {
     return result;
     }
    
     try {
     if (!"Service".equals(className)) //我们需要将 ServiceInterface 由该 ClassLoader 的父类来加载,这一句很重要哦
     return findSystemClass(className);
     } catch (Exception e) {
     }
     try {
     String file = "";
     if ("".equals(this.directory))
     file = className + ".class";
     else
     file = this.directory + File.separatorChar + className + ".class";
    
    classByte = loadClassData(file);
    
    result = defineClass(className, classByte, 0, classByte.length);
     resolveClass(result);
     classes.put(className, result);
     return result;
     } catch(Exception e) {
     System.out.println("Load Class Error in MyClassLoader" + e.getMessage());
     return null;
     }
     }
    
    private byte[] loadClassData(String name) throws IOException {
     InputStream stream = getClass().getClassLoader().getResourceAsStream(name);
     int size = stream.available();
     byte buff[] = new byte[size ];
     DataInputStream in = new DataInputStream(stream);
     in.readFully(buff);
     in.close();
     return buff;
     }
    }

    其中我们定义了一个 Hashtable 用于缓存已经加载过的 class,如果需要加载的 class 之前由该 ClassLoader 加载过,则直接返回。否则按照 findClass 的逻辑进行加载。由于是实验性的程序,因此 MyClassLoader 只会加载名为 Service 的 class,仅用于描述相应现象,实际使用中需要根据情况进行修改。

    有了自定义的 ClassLoader,接下来就是我们希望动态加载的 class 了,为了简单 Service 类就简单的输出一行语句而已。

    public interface ServiceInterface {
     public void run();
    }
    
    public class Service implements ServiceInterface {
     public Service() {
     }
    
    public void run() {
     System.out.println("Service----33---Run");
     }
    }

    有了需要动态加载的 Service 类,然后需要在某个类中能够动态的加载 Service,这里我们使用一个叫 Server 的类来完成这件事,Servier#processRequest 会调用 server#run 方法,Server#updateService 则可以动态的更新调用的 Server 对象

    import java.lang.reflect.*;
    
    public class Server {
     private ServiceInterface service;
     public void updateService(String location) throws Exception {
     MyClassLoader cl = new MyClassLoader(location);
     Class c = cl.loadClass("Service");
     service = (ServiceInterface)c.newInstance();
     }
    
    public void processRequest() throws Exception {
     Class c = service.getClass();
     service.run();
     }
    }

    最后我们需要一个 Main 类,来演示效果,大致如下所示:

    public class Dynamic {
     public static void main(String[] args) throws Exception {
     Server ser = new Server();
     ser.updateService(""); //首先会调用当前路径下 Service#run 方法
     ser.processRequest(); //当前路径下的 Service#run 输出Service----33---Run
     System.out.println("===================");
     ser.updateService("new"); //当前路径下有一个 new 文件夹,new 文件夹中有一个 Service.class 文件,这个文件的 run 函数会打印出 Service----22---Run
     ser.processRequest();//调用 ./new/Service.class#run 方法
     }
    }

    最终我们运行 Dynamic 会看到终端输出如下两行,效果演示完成。

    Service----33---Run
    Service----22---Run

    与 ClassLoader 有关的错误

    上面用一个简单的例子演示了 ClassLoader 动态加载类的功能,下面简单说一下遇到过的与 ClassLoader 相关的错误,以及可能的原因:

    • ClassNotFound : 这是遇见的最常见的一种异常。一般是由于包冲突,导致需要加载的 class 版本不对导致。可以先查看整个工程(包括第三方依赖)有多少指定的 class,然后依次排除即可。
    • ClassCastException : 这是比较少见的一种异常,出现的情况大致为 A 是 B 的子类,但是将 A 转化为 B 的时候出现这样的异常。一般是所属的 ClassLoader 不一样导致。
    • Linkage Constraint violation : 链接的时候异常,大概意思是不同的 ClassLoader L1 和 L2 都加载了一个同名的 class N,而且他们的约束 N^{L1} = N^{L2},但是 L1 和 L2 却从不同的地方加载了 N。更详细的可以参考 The Java Virtual Machine Specification Java SE 8 Edition 的 5.3.4 节 Loading Constraints。

    思考

    • ClassLoader 是否可以看成一个动态配置加载器?class 文件看成配置,如果可以的话,为什么?自己设计需要动态加载配置的系统时怎么借鉴?如果不可以的话为什么?
    • 动态加载的时候,能不能将 service 定义为 Service 而不是 ServiceInterface,为什么?如果不定义为 ServiceInterface 对象,那么是否还有其他方法可以实现同样的效果?其他的方法有哪些?

      参考

    1. Wikipedia:Java Classloader
    2. why-do-you-need-custom-classloader
    3. The Java Virtual Machine Specification Java SE 8 Edition
    您可能也喜欢:

    网络流24题-5 圆桌问题

    Spark Streaming 统一在每分钟的 00 秒消费 Kafka 数据的问题解决

    另类UX让你输入强口令

    XP和Fedora的双系统之路

    C语言编译时的undefined reference to 'sqrt'
    无觅


沪ICP备19023445号-2号
友情链接