SpringBoot基于函数替换的热重载

背景

SpringBoot项目每次启动都很慢,有时候调试仅仅是改一点点东西,就要重启工作效率太低,希望能修改完代码,执行快捷键后就把该类的修改生效。(仅限于Bean的修改生效)

原理

SpringBoot的逻辑基本都是集中在ServiceImpl中,然后调用的时候都是通过@Autowired注入后再调用或者是SpringUtil.getBean获取调用。我们只需要把修改后的类使用Groovy生成一个新对象,然后反向注入到@Autowired标记的变量还有放入到singletonObjects容器中即可。但是相同包名和类名的类不能重复,所以我们获取原有类名加上数字区分然后继承原有类再编译即可。例如:要重载XXX类,那就生成XXX2类并且XXX2继承XXX再new一个XXX2对象反向注入即可。

实现

package com.meal.utils;import com.alibaba.excel.support.cglib.proxy.Enhancer;
import com.alibaba.excel.support.cglib.proxy.MethodInterceptor;
import com.alibaba.excel.support.cglib.proxy.MethodProxy;
import com.meal.system.entity.SysHotfix;
import groovy.lang.GroovyClassLoader;
import lombok.SneakyThrows;import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;public class HotfixUtil {public static Map<String, Object> hotfixMap = new ConcurrentHashMap<>();static int idAdd = 0;//开发使用@SneakyThrowspublic static void reloadFile(String filePath) {System.out.println("重载:"+filePath);File file = new File(filePath);// 获取文件名String fileNameWithExtension = file.getName();// 去掉后缀String fileNameWithoutExtension = fileNameWithExtension.substring(0, fileNameWithExtension.lastIndexOf("."));String beanName = getBeanName(fileNameWithoutExtension);String content = readFile(filePath,fileNameWithoutExtension);if(!SpringUtil.containsBean(beanName)){System.out.println("不存在bean="+beanName);return;}Class clazz = new GroovyClassLoader().parseClass(content);Object o = clazz.newInstance();SpringUtil.replaceBean(beanName, o);}private static String getBeanName(String fileNameWithoutExtension) {if(fileNameWithoutExtension.length() <= 2) {return Character.toLowerCase(fileNameWithoutExtension.charAt(0)) + fileNameWithoutExtension.substring(1);}if(Character.isLowerCase(fileNameWithoutExtension.charAt(0))){return fileNameWithoutExtension;}if(Character.isUpperCase(fileNameWithoutExtension.charAt(0)) && Character.isLowerCase(fileNameWithoutExtension.charAt(1))){//第一个大写 第二个小写return Character.toLowerCase(fileNameWithoutExtension.charAt(0)) + fileNameWithoutExtension.substring(1);}return fileNameWithoutExtension;}public static String readFile(String filePath,String fileNameWithoutExtension) throws IOException {StringBuilder content = new StringBuilder();try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {String line;boolean isLog=false;while ((line = reader.readLine()) != null) {if(line.contains("@Slf4j")) {isLog=true;}if(line.contains("class "+fileNameWithoutExtension)){String newClazz = fileNameWithoutExtension+idAdd;String newLine = line.substring(0,line.indexOf(fileNameWithoutExtension));newLine += newClazz+" extends "+fileNameWithoutExtension;idAdd++;if(line.contains("{")){newLine += " {";}content.append(newLine).append("\n");if(isLog){content.append("Logger log = LoggerFactory.getLogger("+newClazz+".class);").append("\n");}} else if(line.contains("package ")){content.append(line).append("\n");String newLine = line.substring(line.indexOf("package ")+8);newLine = newLine.replace(";","");newLine ="import "+newLine+"."+fileNameWithoutExtension+";";content.append(newLine).append("\n");content.append("import org.slf4j.Logger;").append("\n");content.append("import org.slf4j.LoggerFactory;").append("\n");}else{content.append(line).append("\n");}}}return content.toString();}@SneakyThrowspublic static void reload() {synchronized (hotfixMap) {for (String beanName : hotfixMap.keySet()) {SpringUtil.replaceBean(beanName, hotfixMap.get(beanName));}List<SysHotfix> sysHotfixList = EntityCrudServiceUtil.list(SysHotfix.class, null);for (SysHotfix sysHotfix : sysHotfixList) {Class clazz = new GroovyClassLoader().parseClass(sysHotfix.getClazzCode());Object o = clazz.newInstance();if (!hotfixMap.containsKey(sysHotfix.getBeanName()))hotfixMap.put(sysHotfix.getBeanName(), SpringUtil.getBean(sysHotfix.getBeanName()));SpringUtil.replaceBean(sysHotfix.getBeanName(), o);}}}public static Object createProxy(final Object target,Object bean) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(target.getClass());enhancer.setCallback(new HotfixMethodInterceptor(bean));return enhancer.create();}public static class HotfixMethodInterceptor implements MethodInterceptor {private Object originalObject;public HotfixMethodInterceptor(Object originalObject) {this.originalObject = originalObject;}@Overridepublic Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {return proxy.invokeSuper(object, args);}}
}
package com.meal.utils;import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.yarn.webapp.hamlet.Hamlet;
import org.springframework.aop.framework.AopContext;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.DefaultSingletonBeanRegistry;
import org.springframework.cloud.openfeign.FeignContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;import java.lang.reflect.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;/*** @Author cwj* @Version 20211225* @desc spring工具类*/
@Component
@Lazy
public final class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextAware {/*** Spring应用上下文环境*/private static ConfigurableListableBeanFactory beanFactory;private static ApplicationContext applicationContext;@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {SpringUtil.beanFactory = beanFactory;}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {SpringUtil.applicationContext = applicationContext;}/*** 获取对象** @param name* @return Object 一个以所给名字注册的bean的实例* @throws BeansException*/@SuppressWarnings("unchecked")public static <T> T getBean(String name) throws BeansException {return (T) beanFactory.getBean(name);}/*** 获取类型为requiredType的对象** @param clz* @return* @throws BeansException*/public static <T> T getBean(Class<T> clz) throws BeansException {T result = (T) beanFactory.getBean(clz);return result;}/*** 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true** @param name* @return boolean*/public static boolean containsBean(String name) {return beanFactory.containsBean(name);}/*** 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)** @param name* @return boolean* @throws NoSuchBeanDefinitionException*/public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {return beanFactory.isSingleton(name);}/*** @param name* @return Class 注册对象的类型* @throws NoSuchBeanDefinitionException*/public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {return beanFactory.getType(name);}/*** 如果给定的bean名字在bean定义中有别名,则返回这些别名** @param name* @return* @throws NoSuchBeanDefinitionException*/public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {return beanFactory.getAliases(name);}/*** 获取aop代理对象** @param invoker* @return*/@SuppressWarnings("unchecked")public static <T> T getAopProxy(T invoker) {return (T) AopContext.currentProxy();}/*** 获取当前的环境配置,无配置返回null** @return 当前的环境配置*/public static String[] getActiveProfiles() {return applicationContext.getEnvironment().getActiveProfiles();}/*** 获取当前的环境配置,当有多个环境配置时,只获取第一个** @return 当前的环境配置*/public static String getActiveProfile() {final String[] activeProfiles = getActiveProfiles();return StringUtils.isNoneBlank(activeProfiles) ? activeProfiles[0] : null;}public static <T> T getFeignBean(String beanName, Class<T> tClass) {FeignContext feignContext = applicationContext.getBean("feignContext", FeignContext.class);return feignContext.getInstance(beanName, tClass);}@SneakyThrowspublic static void replaceBean(String beanName, Object targetObj)  {//        cn.hutool.extra.spring.SpringUtil.unregisterBean(beanName);
//        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(targetObj.getClass());
//        BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
//        applicationContext.getAutowireCapableBeanFactory().applyBeanPostProcessorsAfterInitialization(beanDefinition, beanName);
//         ((DefaultListableBeanFactory)beanFactory).registerBeanDefinition(beanName, beanDefinition);
//        applicationContext.getAutowireCapableBeanFactory().autowireBean(targetObj);//        System.out.println("cccccccccc="+map.size());
//
//        System.out.println("WWWWWWWWWWW="+map.size());//        applicationContext.getAutowireCapableBeanFactory().autowireBean(targetObj);injectAutowiredFields(targetObj);//cn.hutool.extra.spring.SpringUtil.unregisterBean(beanName);//ConfigurableApplicationContext context = (ConfigurableApplicationContext)applicationContext;//DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();//反射获取Factory中的singletonObjects 将该名称下的bean进行替换Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");singletonObjects.setAccessible(true);Map<String, Object> map = (Map<String, Object>) singletonObjects.get(beanFactory);//Object bean = map.get(beanName);
//        if(bean != null)
//            cn.hutool.extra.spring.SpringUtil.getConfigurableBeanFactory().destroyBean(bean);// cn.hutool.extra.spring.SpringUtil.registerBean(beanName, targetObj);//beanFactory.registerSingleton(beanName, targetObj);beanFactory.autowireBean(targetObj);map.put(beanName, targetObj);}static Object getTrueSingleton(Object target) {Object result = target;Object trueBean = AopProxyUtils.getSingletonTarget(target);//跳过代理获取真实对象for (int i = 0; i < 10; i++) {if(trueBean != null){result = trueBean;trueBean = AopProxyUtils.getSingletonTarget(trueBean);}}return result;}private static void injectAutowiredFields(Object target) {String[] beanNames = applicationContext.getBeanDefinitionNames();for (String beanName : beanNames) {Object bean = beanFactory.getBean(beanName);Object trueBean = getTrueSingleton(bean);//跳过代理获取真实对象Object finalTrueBean = trueBean;Object trueTarget = getTrueSingleton(target);//跳过代理获取真实对象Object finalTrueTarget = trueTarget;ReflectionUtils.doWithFields(trueBean.getClass(), field -> {if (field.isAnnotationPresent(Autowired.class)) {if(field.getType().isAssignableFrom(finalTrueTarget.getClass())) {
//                        FactoryBean<?> factoryBean = (FactoryBean<?>) bean;
//                        try {
//                            Object originalObject = factoryBean.getObject();
//                            field.setAccessible(true);
//                            System.out.println("RRRRRRRRRRRRR0="+field.get(originalObject));
//
//                            field.set(originalObject, null);
//
//                            field.set(originalObject, (Object) target);
//                            System.out.println("RRRRRRRRRRRRR1="+field.get(originalObject));
//                        } catch (Exception e) {
//                            e.printStackTrace();
//                        }field.setAccessible(true);field.set(finalTrueBean, target);}}});//            if(beanName.contains("webSocketConfig")) {
//                System.out.println("AAAAAAAAA="+beanName);
//            }Method[] methods = trueBean.getClass().getMethods();for (Method method : methods) {if (method.isAnnotationPresent(Autowired.class) && method.getName().startsWith("set")) {// 获取方法的参数类型Class<?>[] parameterTypes = method.getParameterTypes();if (parameterTypes.length == 1) {// 查找相应类型的 beantry {Object autowiredBean = applicationContext.getBean(parameterTypes[0]);
//                            if(beanName.contains("webSocketConfig")) {
//                                System.out.println("BBBBBB="+autowiredBean);
//                            }method.setAccessible(true);// 调用 setter 方法注入 beanmethod.invoke(finalTrueBean, target);} catch (Exception e) {
//                            if(beanName.contains("webSocketConfig")) {
//                                e.printStackTrace();
//                            }}}}}//            System.out.println("EEEEEEE="+beanName);
//            Class<?> beanClass = bean.getClass();
//            Field[] fields = beanClass.getDeclaredFields();
//            for (Field field : fields) {
//                Type fieldType1 = field.getGenericType();
//                // 判断对象是否是字段 field1 的泛型类型或其子类型
//                if (fieldType1 instanceof ParameterizedType) {
//                    ParameterizedType parameterizedType = (ParameterizedType) fieldType1;
//                    Type[] typeArguments = parameterizedType.getActualTypeArguments();
//                    // 这里假设 listField 是 List<String> 类型
//                    if (typeArguments.length > 0 && typeArguments[0] instanceof Class) {
//                        Class<?> genericType = (Class<?>) typeArguments[0];
//                        // 判断对象是否是字段 field1 的泛型类型或其子类型
//                        if (genericType.isAssignableFrom(target.getClass())) {
//                            System.out.println("对象是字段 field1 的泛型类型或其子类型");
//                        } else {
//                            System.out.println("对象不是字段 field1 的泛型类型或其子类型");
//                        }
//                    }
//                }//                if (field.isAnnotationPresent(Autowired.class)) {
//field.setAccessible(true);field.set(bean, null);
//                    field.setAccessible(true);
//                    try {
//                        field.set(bean, target);
//                    } catch (IllegalAccessException e) {
//                        e.printStackTrace();
//                    }
//
//                }}}}

使用

需要自己实现一个idea插件,用于监控快捷键触发,获取当前选中的文件路径,然后通过http请求发送到自己项目,项目再执行HotfixUtil.reloadFile即可热更新模块。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/15535.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ViT:1 从DETR说起

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调重新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则提供了大模型领域最新技…

Pycharm在下载安装第三方库时速度慢或超时问题 / 切换国内镜像地址

pycharm下载第三方库速度极慢&#xff0c;搜索了一下&#xff0c;发现方法非常乱&#xff0c;稍作整理。这个问题一般都会出现&#xff0c;在我们开发中遇到的常见问题&#xff0c;根据以下解决方法&#xff0c;基本可以解决&#xff0c;但是不能100%保证 Installing packages …

打造一个增强版Kimi:可以生成图片、PPT、PDF文档、数据分析等

Kimi虽然在国内AI大模型中表现不错&#xff0c;但是和ChatGPT还是差不少功能。现在有一个很简单的方法&#xff0c;把kimi功能增强&#xff0c;使用效果大大改善&#xff0c;比如生成图片&#xff1a; 具体方法如下&#xff1a; 打开coze网站&#xff1a;https://www.coze.cn/…

Elementui里使用el-date-picker来选取指定时间段(时间段不超过31天)

需求描述&#xff1a; 1.禁止选择当前日期之后的所有日期2.选择的时间范围小于等于31天&#xff0c;其他日期禁用<el-date-picker v-model"historySubmitModel.historyDateTime" type"daterange" range-separator"- " start-placeholder"…

鸿蒙应用开发系列 篇四:鸿蒙系统应用开发基础

文章目录 系列文章概述ArkTS应用(Stage模型)示例应用示例代码使用模拟器运行应用使用真机运行应用应用程序包共享包应用配置文件(Stage模型)资源目录示例系列文章 鸿蒙应用开发系列 篇一:鸿蒙系统概述 鸿蒙应用开发系列 篇二:鸿蒙系统开发工具与环境

【c++基础】昆虫繁殖

说明 科学家在热带森林中发现了一种特殊的昆虫&#xff0c;这种昆虫的繁殖能力很强。每对成虫每过x个月产y对卵&#xff0c;每对卵要过两个月长成成虫。假设每个成虫不死&#xff0c;第一个月只有一对成虫&#xff0c;且卵长成成虫后的第一个月不产卵(过X个月产卵)&#xff0c…

C++容器之位集(std::bitset)

目录 1 概述2 使用实例3 接口使用3.1 constructor3.2 count_and_size3.3 test3.4 any3.5 none3.6 all3.7 set3.8 reset3.9 filp3.10 to_string3.11 to_ulong3.12 to_ullong3.13 operators1 概述 位集存储位(只有两个可能值的元素:0或1,true或false,…)。   该类模拟bool…

推荐一款自助分析的财务分析软件:奥威BI软件

奥威BI软件是一款支持多维度动态自助分析的软件&#xff0c;预设了智能财务分析方案&#xff0c;提供内存行列计算模型解决财务指标计算难题&#xff0c;界面简洁&#xff0c;以点击、拖曳操作为主&#xff0c;十分适合没有IT背景的财务人做财务分析。因此也经常有人说奥威BI软…

Spark搭建 Standalone模式详细步骤

Standalone模式概述&#xff1a; Standalone模式是Spark自带的一种集群模式&#xff08;本地集群&#xff0c;不依赖与外部集群&#xff0c;比如Yarn&#xff09;&#xff0c;可以真实地在多个机器之间搭建Spark集群的环境。 Standalone是完整的Spark运行环境,其中: Master角…

OpenFeign微服务调用组件使用

前言&#xff1a;OpenFeign是可以跨服务、跨进程的调用方式。 什么是Feign Feign是Netflix开发的声明式、模版化的HTTP客户端。 优势: Feign可以做到使用 HTTP 请求远程服务时就像调用本地方法一样的体验&#xff0c;开发者完全感知不到这是远程方法&#xff0c;更感知不到这…

【TB作品】stm32单片机读取DS2401程序

DS2401是由Analog Devices公司生产的一种硅序列号芯片&#xff0c;它提供了一个绝对唯一的64位ROM识别码&#xff0c;用于确保可追溯性。以下是对DS2401器件的分析&#xff1a; 特点和优势&#xff1a; 唯一性&#xff1a;每个DS2401芯片都有一个独一无二的64位注册码&#x…

PointPillars, CenterPoint,和TransFusion

PointPillars, CenterPoint, and TransFusion These models are primarily used for 3D object detection in autonomous driving. Here is a brief introduction to PointPillars, CenterPoint, and TransFusion: PointPillars 领域&#xff1a;计算机视觉&#xff0c;自动…

[less配置]vue2引入less

1、终端输入&#xff1a;npm install less less-loader --save-dev 2、在package.json查看是否安装less依赖 3、调用

vue2快速安装环境,从0-1创建vue2项目教程

vue2快速安装环境&#xff0c;从0-1创建vue2项目教程(windows) 一、node下载 1.如何查看node版本和npm版本 二、npm安装脚手架 1.注意事项 三、vue2选项解读 四、运行脚手架 一、node下载 1、(node.js中文网) 下载长期稳定版本就行 解释下node.js和npm的关系? 想象你在…

原始字面常量(C++11)

原始字面常量&#xff08;C11&#xff09; 文章目录 原始字面常量&#xff08;C11&#xff09;前言一、原始字面量二、代码示例总结 前言 字面量一般是指数值&#xff08;12、454等&#xff09;和字符串&#xff08;“Hw”、“h\t”&#xff09;&#xff0c;但是有时候我们想表…

leetcode题目274

H指数 中等 给你一个整数数组 citations &#xff0c;其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。 根据维基百科上 h 指数的定义&#xff1a;h 代表“高引用次数” &#xff0c;一名科研人员的 h 指数 是指他&#xff08;她&…

Android Studio 问题集锦

报 Plugin [id: ‘com.android.application’, version: ‘8.1.3’, apply: false] was not found in any of the following sources: 场景&#xff1a;在一个Android 11的项目中添加一个Android 9的项目作为其Module&#xff0c;结果导致原项目无法正常运行&#xff0c;且原项…

PyTorch安装与配置

前言 参考文档&#xff1a;https://github.com/TingsongYu/PyTorch-Tutorial-2nd 环境配置之Anaconda 解释器——python.exe&#xff0c;是人类与CPU之间的桥梁&#xff0c;需要配置系统环境变量 Anaconda&#xff1a;集成环境&#xff0c;包管理器 Conda 安装 Anaconda&am…

WXSS模板样式-全局样式和局部样式

一、WXSS 1.WXSS WXSS(WeiXin Style Sheets)是一套样式语言&#xff0c;用于美化WXML的组件样式&#xff0c;类似于网页开发中的CSS 2.WXSS和CSS的关系 WXSS具有CSS大部分特性&#xff0c;同时&#xff0c;WXSS还对CSS进行了扩充以及修改&#xff0c;以适应微信小程序的开发…

PropertyGrid显示嵌套对象

简介&#xff1a; 在C#中&#xff0c;使用PropertyGrid来显示多重变量&#xff0c;通常意味着你想要展示一个对象的属性&#xff0c;该对象包含子对象或者集合。以下是一个简单的例子&#xff0c;展示如何使用PropertyGrid来显示包含嵌套属性的对象。 使用&#xff1a; 嵌套对象…