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;
}