赞
踩
在平时学习和开发过程中,如果java源文件发生了修改会重新编译成新的class文件,这时一般都需要重新启动项目,加载最新的class文件,改动才会生效。那么能不能在不重新启动项目的情况下,动态更新掉class文件,使最新的改动生效呢,下面我们简单实践一下:
首先创建一个简单的类文件:
package learnbymaven.string;
/**
* @author jinghx
* @date 2020/06/03
*/
public class Hello {
public String sayHello(String content) {
return "hello " + content;
}
}
将生成的class文件放到E盘根目录下:
创建一个spring boot工程,项目结构如下:
导入maven依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
既然涉及到类加载,这里先自己定义一个类加载器:
public class MyClassLoader extends ClassLoader { /** * 需要加载类的路径 */ private String classPath; public MyClassLoader() { } public MyClassLoader(String classPath) { super(); this.classPath = classPath; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class<?> clazz = null; // 获取class文件字节码数组 byte[] clazzByteArr = getData(); if (clazzByteArr != null) { // 将class的字节码数组转换成class类的实例 clazz = defineClass(name, clazzByteArr, 0, clazzByteArr.length); } return clazz; } /** * 获取class文件字节数组 * * @return */ private byte[] getData() { File file = new File(this.classPath); if (file.exists()) { FileInputStream in = null; ByteArrayOutputStream out = null; try { in = new FileInputStream(file); out = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int size = 0; while ((size = in.read(buffer)) != -1) { out.write(buffer, 0, size); } } catch (IOException e) { e.printStackTrace(); } finally { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } return out.toByteArray(); } else { return null; } } public String getClassPath() { return classPath; } public void setClassPath(String classPath) { this.classPath = classPath; } }
测试一下自定义类加载器是否可以使用:
@Test
void contextLoads() throws Exception {
MyClassLoader myClassLoader = new MyClassLoader();
myClassLoader.setClassPath("E:\\Hello.class");
Class<?> clazz = myClassLoader.loadClass("learnbymaven.string.Hello");
System.out.println("当前类加载器:" + clazz.getClassLoader());
Object instance = clazz.newInstance();
Method method = clazz.getMethod("sayHello", String.class);
Object result = method.invoke(instance, "李四");
System.out.println(result);
}
运行结果:
当前类加载器:com.learn.util.MyClassLoader@2f1ea80d
hello 李四
创建一个class文件加载的工具类:
public class UserUtil { /** * 字节码对象 */ private static Class<?> clazz; /** * 实例对象 */ private static Object userObject; /** * 默认class文件路径 */ private static final String DEFAULT_CLASS_PATH = "E:\\Hello.class"; /** * 默认全限定类名称 */ private static final String DEFAULT_CLASS_NAME = "learnbymaven.string.Hello"; /** * 加载class对象 * * @param classPath * @param className * @return * @throws Exception */ public synchronized static void loadClass(String classPath, String className) throws Exception { MyClassLoader myClassLoader = new MyClassLoader(); myClassLoader.setClassPath(StringUtils.isEmpty(classPath) ? DEFAULT_CLASS_PATH : classPath); clazz = myClassLoader.loadClass(StringUtils.isEmpty(className) ? DEFAULT_CLASS_NAME : className); initUserObject(); } /** * 初始化userObject对象 * * @throws Exception */ public static void initUserObject() throws Exception { userObject = clazz == null ? null : clazz.newInstance(); } /** * sayHello方法 * * @param name * @return * @throws Exception */ public static String sayHello(String name) throws Exception { Method method = clazz.getMethod("sayHello", String.class); return (String) method.invoke(userObject, name); } }
这个工具类主要是实现加载class文件和运行指定的sayHello()方法。
创建一个UserService类,这里只是简单模拟业务,就不再严格遵守开发规范了:
@Service
public class UserService {
public String sayHello(String name) throws Exception {
return UserUtil.sayHello(name);
}
}
创建UserController:
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @GetMapping("/sayhello") public String sayHello(String name) { String result = null; try { result = userService.sayHello(name); } catch (Exception e) { e.printStackTrace(); } return result; } }
为了实现在网页前端能够动态更新class文件,再创建一个ClassController:
@RestController @RequestMapping("/class") public class ClassController { /** * 刷新加载类class文件 * * @param classPath * @param className * @return */ @GetMapping("/flushClass") public String flushClass(String classPath, String className) { String flag = "succes"; try { UserUtil.loadClass(classPath, className); } catch (Exception e) { e.printStackTrace(); flag = "error"; } return flag; } }
为了实现spring boot项目第一次启动时,Hello类就能够实现加载,需要在创建一个InitClassLoad类,InitClassLoad类实现了CommandLineRunner接口:
@Component
@Order(2)
public class InitClassLoad implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
UserUtil.loadClass(null, null);// 加载默认class文件
}
}
到这里,代码编写基本完成了,下面进行测试,运行项目,访问:http://127.0.0.1:8080/user/sayhello?name=王麻子
运行结果:
这时,假设增加了一个需求,需要你额外显示每次问好的时间,Hello类代码如下:
public class Hello {
public String sayHello(String content) {
StringBuilder hello = new StringBuilder();
hello.append("hello, ").append(content).append(" Time:")
.append(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
return hello.toString();
}
}
此时,你只需要用最新的class文件替换掉E盘下面老的class文件,然后访问http://127.0.0.1:8080/class/flushClass?classPath=E:%2F%2FHello.class&className=learnbymaven.string.Hello,动态刷新一下class文件的加载就行了。
注意:使用GET方式提交时需要注意一些特殊字符,\ 表示目录路径 使用%2F替换一下!
然后重新访问:http://127.0.0.1:8080/user/sayhello?name=王麻子
运行结果:
可以看到运行结果已经发生了改变,而整个过程中,我们都没有重启spring boot项目。
到这里,就简单实现了在spring boot项目运行过程中动态替换class文件了,当然这个项目十分简陋,还有很大的提升空间。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。