zoukankan      html  css  js  c++  java
  • 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文件了,当然这个项目十分简陋,还有很大的提升空间。

    一颗安安静静的小韭菜。文中如果有什么错误,欢迎指出。
  • 相关阅读:
    PAT 甲级 1115 Counting Nodes in a BST (30 分)
    PAT 甲级 1114 Family Property (25 分)
    PAT 甲级 1114 Family Property (25 分)
    Python Ethical Hacking
    Python Ethical Hacking
    Python Ethical Hacking
    Python Ethical Hacking
    Python Ethical Hacking
    Python Ethical Hacking
    Python Ethical Hacking
  • 原文地址:https://www.cnblogs.com/c-Ajing/p/13448339.html
Copyright © 2011-2022 走看看