• spring boot项目运行过程中,动态更新class文件,简单实践


    在平时学习和开发过程中,如果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文件了,当然这个项目十分简陋,还有很大的提升空间。

    一颗安安静静的小韭菜。文中如果有什么错误,欢迎指出。
  • 相关阅读:
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    C# List分组
    Win7安装VS2019
    C# Lambda Left Join AND Group by Then Sum
    RSA加密解密,Base64String
    Ion-select and ion-option list styling 自定义样式
    Docker镜像
  • 原文地址:https://www.cnblogs.com/c-Ajing/p/13448339.html
Copyright © 2020-2023  润新知