热部署instrumentation工具类

简介: 利用jvm的instrumentation类实现实时修改class文件,用来更新线上代码。

import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.agent.ByteBuddyAgent;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.lang.instrument.Instrumentation;
import java.util.concurrent.TimeUnit;

@Slf4j
@RestController
public class ReloadClassUtil {

/**
 * 手动上传class文件实时更新jvm内存中的字节码文件
 * @param classFile newClassFile
 * @param classPath com.xxx.xxx.xxx
 * @return success/fail
 */
@PostMapping("/reloadClass")
public Result<String> reloadClass(List<MultipartFile> classFiles, String classPath) {
    log.info("reloadClass start, classPath:{}", classPath);
    if (!permission) return new Result<>(ResultCodeEnum.UNAUTHORIZED);
    return reTransformClass(classFiles, classPath);
}

private Result<String> reTransformClass(List<MultipartFile> files, String path){
    try {
        Map<String, String> fileByteMap = new HashMap<>();
        for (MultipartFile file : files) {
            String className = path + "." + file.getOriginalFilename().replace(".class", "");
            reTransform(file.getBytes(), className);
            fileByteMap.put(className, Base64Utils.encodeBase64String(file.getBytes()));
        }
        syncOthersClient(fileByteMap);
        return new Result<>(ResultCodeEnum.SUCCESS);
    } catch (Exception e) {
        log.error("reloadClass exception", e);
        return new Result<>(ResultCodeEnum.INTERNAL_SERVER_ERROR, e.getMessage());
    }
}

/**
 * 重载class
 * @param fileBytes 字节码文件
 * @param classPath 目标包+类名
 */
public static boolean reTransform(byte[] fileBytes, String classPath) throws Exception{
    inst = inst != null ? inst : ByteBuddyAgent.install();
    String targetClass = classPath.replace(".", "/");
    inst.addTransformer((loader, className, classBeingRedefined, protectionDomain, classfileBuffer) ->
            className.equals(targetClass) ? fileBytes : classfileBuffer, true);
    inst.retransformClasses(Class.forName(classPath));
    return true;
}

/**
 * 同步class文件给集群内其他服务器
 */
private void syncOthersClient(Map<String, String> fileByteMap){
    StringBuilder sb = new StringBuilder();
    fileByteMap.forEach((k,v) -> {
        sb.append(k).append(",");
        redisTemplate.opsForValue().set(k, v, cacheSecond, TimeUnit.SECONDS);
    });
    ReloadClassDto body = ReloadClassDto.builder().classPath(sb.substring(0, sb.length()-1)).build();
    mq.sendMsg(body);
}

@Value("${reload.cacheSecond:60}")
private long cacheSecond;

@Value("${reload.permission:false}")
private boolean permission;

private static Instrumentation inst;

@Resource
private RedisTemplate<String, String> redisTemplate;

}

相关文章
|
运维 Kubernetes Cloud Native
莉莉丝游戏云原生之路
本文将介绍莉莉丝游戏云原生化的背景、挑战,以及应对的解决方案,记录了莉莉丝游戏云原生化历程,为游戏架构云原生转型提供经验。
莉莉丝游戏云原生之路
|
移动开发 网络协议 算法
TCP中的粘包、拆包问题产生原因及解决方法
TCP中的粘包、拆包问题产生原因及解决方法
2370 0
TCP中的粘包、拆包问题产生原因及解决方法
|
消息中间件 存储 Kafka
RocketMQ 工作原理图解,看这篇就够了!
本文详细解析了 RocketMQ 的核心架构、消息领域模型、关键特性和应用场景,帮助深入理解消息中间件的工作原理。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
RocketMQ 工作原理图解,看这篇就够了!
|
Ubuntu 网络安全 数据安全/隐私保护
访问仓库
访问仓库
514 0
|
Linux Docker 容器
更改docker容器中的时间而不影响宿主机
更改docker容器中的时间而不影响宿主机
|
缓存 网络协议 算法
TCP粘包、拆包原因与解决方案
TCP粘包、拆包原因与解决方案
1060 0
|
消息中间件 存储 负载均衡
『RabbitMQ』手把手带你搭建RabbitMQ镜像集群
📣读完这篇文章里你能收获到 - RabbitMQ多种模式的区别及概念 - 手把手带你搭建高可用集群 - 接入HaProxy做高可用负载均衡 - 针对生产突发的异常场景提进行测试验证
4991 0
『RabbitMQ』手把手带你搭建RabbitMQ镜像集群
SpringCloud - Eureka 自我保护机制
SpringCloud - Eureka 自我保护机制
415 0
SpringCloud - Eureka 自我保护机制
|
Java Android开发 Windows

热门文章

最新文章