这里所说的 ClassLoader 均指 Java 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:
${JAVA_HOME}/jre/lib
目录下的 class${JAVA_HOME}/jre/lib/ext
目录下的 classjava.class.path
路径下的 class,java.class.path
即用户设定的 CLASSPATH
当然,用户还可以根据需要定义自己的 ClassLoader,只需要继承 ClassLoader
类即可。
ClassLoader 的核心工作是用于加载 class,那么除了系统提供的 ClassLoader 外,我们是否还需要自定义的 ClassLoader 呢?在什么情况下会需要自定义的 ClassLoader 呢?
我们先看自定义的 ClassLoader 能提供哪些功能:
如果我们希望实现上面功能中的任何一种,都需要实现自定义的 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 的主要原因,有些服务起来之后,希望能够 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 相关的错误,以及可能的原因:
A
是 B
的子类,但是将 A
转化为 B
的时候出现这样的异常。一般是所属的 ClassLoader 不一样导致。L1
和 L2
都加载了一个同名的 class N
,而且他们的约束 L1
和 L2
却从不同的地方加载了 N
。更详细的可以参考 The Java Virtual Machine Specification Java SE 8 Edition 的 5.3.4 节 Loading Constraints。service
定义为 Service
而不是 ServiceInterface
,为什么?如果不定义为 ServiceInterface
对象,那么是否还有其他方法可以实现同样的效果?其他的方法有哪些?您可能也喜欢: | ||||
![]() 网络流24题-5 圆桌问题 |
![]() Spark Streaming 统一在每分钟的 00 秒消费 Kafka 数据的问题解决 |
![]() 另类UX让你输入强口令 |
![]() XP和Fedora的双系统之路 |
![]() C语言编译时的undefined reference to 'sqrt' |
无觅 |