通过ClassLoader实现容器热部署实例

本文介绍了J2EE环境中实现热部署的技术细节,包括自定义ClassLoader以支持动态加载已修改的类,以及如何通过监听机制动态加载更新后的类。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在J2EE的项目中,容器给我们提供的热部署功能使得我们不用重启动容器而修改我们的代码。比如使用Weblogic,我们可以在Weblogic-application.xml中配置是否支持热部署Servlet。查阅Weblogc 文档,其实在Weblogic中,EJB组件也是可以热部署的,但如果要热部署EJB组件,Weblogc要求必须自定义ClassLoder。

    JVM规范中没有指定JVM支持动态加载修改过的类。类的加载,卸载对于程序员是透明的。如果我们要实现类的动态加载,我们就要理解JVM本身类的加载与 卸载的原理,实现热部署。对于JVM加载类方面的资料在网上很多的,在这里我做简单概述:
    (1)JVM加载时通过ClassLoader加载的。
    (2)JVM有3层继承关系的ClassLoder 分别是:
             -----BootStrap类加载器 加载JRE/lib
             -----ExtClassLoader 加载 JRE /lib/ext
             -----AppClassLoader 加载ClassPath/

 


    (3)为了安全性,JVM加载采用了双亲委派机制,如何理解呢,就是当需要加载一个类时,当前的ClassLoader先请求父ClassLoader,依次
      类推,直到父类的ClassLoader无法加载时,才通过当前的ClassLoser加载,这就保证了像String这样的类型必须使用JRE里面的, 使得JRE lib 下的类不会被修改。同时避免了ClassCaseException。
   (4)在JVM中,一个实例是通过本身的类名+加载它的ClassLoader识别的,也就是说 不同的ClassLoader 加载同一个类在JVM是不同的。
   (5)同一个ClassLoader是不允许多次加载一个类的,否则会报java.lang.LinkageError。attempted  duplicate class definition for   name XXX,在下面的例子中会指出。
    既然JVM不支持热部署,那么要实现热部署,就必须自定义ClassLoader,当类被修改过后,加载该类。下面通过代码说明:

// 自定义一个类加载器 public class DynamicClassLoader extends ClassLoader { public Class<?> findClass(byte[] b) throws ClassNotFoundException { return defineClass(null, b, 0, b.length); }

defineClass方法会把class的byte数组加载起来

import java.io.*; public class ManageClassLoader { DynamicClassLoader dc =null; Long lastModified = 0l; Class c = null; //加载类, 如果类文件修改过加载,如果没有修改,返回当前的 public Class loadClass(String name) throws ClassNotFoundException, IOException{ if (isClassModified(name)){ dc = new DynamicClassLoader(); return c = dc.findClass(getBytes(name)); } return c; } //判断是否被修改过 private boolean isClassModified(String filename) { boolean returnValue = false; File file = new File(filename); if (file.lastModified() > lastModified) { returnValue = true; } return returnValue; } // 从本地读取文件 private byte[] getBytes(String filename) throws IOException { File file = new File(filename); long len = file.length(); lastModified = file.lastModified(); byte raw[] = new byte[(int) len]; FileInputStream fin = new FileInputStream(file); int r = fin.read(raw); if (r != len) { throw new IOException("Can't read all, " + r + " != " + len); } fin.close(); return raw; } }

测试类;Main 每隔 5s 加载一次
import java.io.*; import java.lang.reflect.*; public class Main { /** * @param args the command line arguments */ public static void main(String[] args) throws ClassNotFoundException, IOException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException, InterruptedException { String path = "c:\\java\\LocalClass.class"; ManageClassLoader mc = new ManageClassLoader(); while(true){ Class c = mc.loadClass(path); Object o = c.newInstance(); Method m = c.getMethod("getName"); m.invoke(o); System.out.println(c.getClassLoader()); Thread.sleep(5000); } }

被加载的类
public class LocalClass { public void getName() { System.out.println("hahaha "); } }

运行时,每隔5s 输出:
hahaha
classloader.DynamicClassLoader@61de33
当我们修改 System.out.println( " hahaha  " ); ---> System.out.println( " changed  " ); 编译LocalClass后
输出变为:
changed
classloader.DynamicClassLoader@173a10f

loadClass 中, 我们必须重新初始化一个ClassLoader,否则就会违背同一个ClassLoader是不允许多次加载一个类的。
当然,容器的实现机制肯定要完善,不可能周期性的加载,可能回通过监听机制,动态加载修改过的类。但它的实现机制肯定也是重新 实例化一个ClassLoder,加载需要加载的类。

### Java加载器在热部署插件中的应用原理 Java加载器的核心功能在于动态加载文件并将其映射到JVM内存中。这种特性使得基于加载器的热部署技术成为可能。以下是关于Java加载器实现热部署插件机制的主要原理: #### 1. **加载器隔离** 为了支持热部署,通常会创建一个新的加载器实例来加载修改后的文件。这样可以避免旧版本的影响,从而实现在运行时不重启应用程序的情况下更新代码逻辑。新的加载器仅加载新版本的文件,而不会影响其他未更改的部分[^1]。 ```java // 创建自定义加载器用于加载特定目录下的 public class CustomClassLoader extends ClassLoader { private final File directory; public CustomClassLoader(File directory) { this.directory = directory; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classData = loadClassFromFile(name); if (classData == null) { throw new ClassNotFoundException(); } else { return defineClass(name, classData, 0, classData.length); } } private byte[] loadClassFromFile(String className) { String path = directory.getAbsolutePath() + File.separatorChar + className.replace('.', File.separatorChar) + ".class"; try (InputStream inputStream = new FileInputStream(path); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { int nextByte; while ((nextByte = inputStream.read()) != -1) { byteArrayOutputStream.write(nextByte); } return byteArrayOutputStream.toByteArray(); } catch (IOException e) { return null; } } } ``` 上述代码展示了如何通过自定义加载器加载指定目录下的`.class`文件。每次重新编译后,可以通过更换目标目录的方式加载最新版本的文件[^2]。 --- #### 2. **卸载与垃圾回收** 由于Java的一旦被加载就无法直接卸载,因此需要依赖于加载器的生命周期管理。当某个加载器不再有强引用时,其加载的所有都可以被垃圾回收器清理掉。这为热部署提供了理论依据——销毁旧的加载器即可释放资源,并允许后续使用新的加载器加载更新后的文件[^3]。 --- #### 3. **字节码操作与Instrumentation API** 对于更复杂的场景(如方法签名变更或新增字段),单纯依靠加载器切换不足以满足需求。此时可借助Java Instrumentation API,在不中断程序运行的前提下完成的重定义。该API允许开发者在运行时修改已加载的行为,具体方式如下: - 使用`redefineClasses()`方法替换现有的字节码。 - 结合ASM、Javassist等字节码操作框架生成新的定义。 示例代码展示如何利用Instrumentation API实现的热替换: ```java import java.lang.instrument.Instrumentation; public class Agent { private static volatile Instrumentation globalInst; public static void premain(String agentArgs, Instrumentation inst) { globalInst = inst; } public static boolean redefine(Class<?> targetCls, byte[] updatedBytes) { if (globalInst == null || !targetCls.isInstance(targetCls)) { return false; } try { globalInst.redefineClasses(new ClassDefinition(targetCls, updatedBytes)); return true; } catch (Exception ex) { ex.printStackTrace(); return false; } } } ``` 此代码片段实现了通过Agent预置Instrumentation对象的功能,便于后续调用`redefineClasses()`接口完成的实时更新。 --- #### 4. **实际应用场景** 在现代Web容器(如Tomcat)和微服务架构中,热部署已经成为标配功能之一。例如,Spring Boot DevTools通过对`RestartClassLoader`的设计,能够快速检测源码变化并触发局部刷新;而像JRebel这样的商业工具则进一步优化了性能开销较大的全量重建过程。 --- ### 总结 综上所述,Java加载器在热部署插件中的应用主要体现在以下几个方面:通过多层加载器体系实现模块化隔离;结合垃圾回收机制确保资源有效释放;以及引入高级工具和技术(如Instrumentation API)增强灵活性和支持复杂场景的能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值