好久没写过博客了,今天突然发现之前的一篇博客能够给到一位老哥帮助,让我很高兴,遂起了继续写博客的想法,说不定自己这些不咋样的文章能够帮助到一些人,也顺便自己记录下学习内容,┑( ̄Д  ̄)┍
公司项目中使用k8s集群作为测试、正式环境,在测试阶段,对于很小的改动都会导致我重新推送重启发布,该流程在会有2~3分钟,很是麻烦
提升改bug的效率,减少重新推送重启发布的流程
改动类方法的定义,即修改方法中的一些代码,添加类静态变量,可以本地直接运行main将改动提交到测试环境,rancher中,这样就不需要重新发布重启了
但是不支持类属性、方法的改动,不能引用新的方法类等(这是我这边使用时会有这些问题)
说明:代码流程如下
其中的文件服务器该处用的是我服务器nginx配的,能用就行。该集成springboot的代码应该是能直接用的,毕竟现在这些代码是我目前再用的
注意 /app.jar 是用来匹配到具体的jvm的,可以在rancher节点执行命令行,通过jps看
注意 依赖jdk的tools,像我们默认没有加载tools所以在这里载入注意 本地springboot可以载入项目的java环境中的tools
com.sun
tools
1.8
system
${java.home}/../lib/tools.jar
@GetMapping("/roger/editClass")
public void editClass(String startApplicationPackageNameAndClassName, String params) {
JvmAgent.controllerAttach(startApplicationPackageNameAndClassName, params);
}
static class JvmAgent {
public static void main(String[] args) throws IOException {
long l = System.currentTimeMillis();
Class>[] classPath = {ResourceCenterServiceImpl.class};
// String s = localPerform(classPath, "http://localhost:8080/media/demo/roger/editClass", "cn.thecover.data.media.MediaApplication");
//注意,/app.jar 是用来匹配到具体的jvm的,可以在rancher节点执行命令行,通过jps看
String s = localPerform(classPath, "https://localhost:8080/demo/roger/editClass", "/app.jar", new HashMap(2){{
put("Authorization", "eyJhbGciOiJIUzUxMiIsInppcCI6IkRFRiJ9.eNoUjE0OwiAQhe8ya0kYqJT2Bu7dm4GOSWukBgYTY7y70937-d77wiYrzGBdxDB4bxakZAYXo5kSZuMDZhqZ7tZGOEHrSWGnam1NlewPLqZxfXM9QhKYMZwnj3EMVvm918zHv7bChYpcFrX1KTfpVLauedf99fNSDH9_AAAA__8.3aCVmcpfxc43VFuLcew2_5uyfb6Td5fcm_4g6qwpat8-RmgzhZjtJdz2m13FsFeJvHX5WYjvPMQ1r6ZvoLDJsQ");
}});
System.out.println(s);
System.out.println("耗时:"+(System.currentTimeMillis()-l));
}
public static void controllerAttach(String startApplicationPackageNameAndClassName, String params){
String jarPath = "http://139.9.87.17:30020/file/CommonJvmAgent.jar";
try {
attach(startApplicationPackageNameAndClassName, jarPath, params);
} catch (IOException | AttachNotSupportedException | AgentLoadException | AgentInitializationException e) {
e.printStackTrace();
}
}
public static String localPerform(Class>[] clas, String controllerPath, String startApplicationPackageNameAndClassName, Map header) throws IOException {
List
import jdk.nashorn.api.scripting.JSObject;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.util.HashMap;
import java.util.Map;
public class JpAgent {
public static void premain(String args, Instrumentation inst) {
System.out.println("premain");
}
public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("nashorn");
System.out.println("输入传参:"+agentArgs);
try {
scriptEngine.eval("function getJson(){return " + agentArgs + "}");
Invocable in = (Invocable) scriptEngine;
JSObject getJson = (JSObject) in.invokeFunction("getJson");
Map collect = new HashMap<>();
for (Object value : getJson.values()) {
Map value1 = (Map) value;
String packageNameAndClassName = value1.get("packageNameAndClassName");
String classUrl = value1.get("classUrl");
if (packageNameAndClassName != null && classUrl != null) {
collect.put(packageNameAndClassName, classUrl);
}
}
System.out.println("参数解析:"+collect);
inst.addTransformer(new JpClassFileTransformer(collect), true);
for (Class allLoadedClass : inst.getAllLoadedClasses()) {
String s = collect.get(allLoadedClass.getName());
if(s != null){
System.out.println("重新定义class:"+allLoadedClass.getName());
// ClassDefinition classDefinition = new ClassDefinition(allLoadedClass, JpClassFileTransformer.readUrlFile(s));
// inst.redefineClasses(classDefinition);
inst.retransformClasses(allLoadedClass);
}
}
} catch (ScriptException | NoSuchMethodException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("nashorn");
String s = "[{\"classUrl\":\"http://139.9.87.17:30020/file/temp/4d36413c-3de4-4075-859b-fdeb9ebcf1b7.class\",\"packageNameAndClassName\":\"com.Dog\"}]";
System.out.println(s);
try {
scriptEngine.eval("function getJson(){return " + s + "}");
Invocable in = (Invocable) scriptEngine;
JSObject getJson = (JSObject) in.invokeFunction("getJson");
for (Object value : getJson.values()) {
Map value1 = (Map) value;
System.out.println(value1.get("classUrl"));
}
} catch (ScriptException | NoSuchMethodException e) {
e.printStackTrace();
}
}
}
import java.io.*;
import java.lang.instrument.ClassFileTransformer;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.security.ProtectionDomain;
import java.util.Map;
public class JpClassFileTransformer implements ClassFileTransformer {
private Map classToUrl;
public JpClassFileTransformer(Map classToUrl) {
this.classToUrl = classToUrl;
}
@Override
public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) {
// System.out.println("loader className: " + className);
String s = classToUrl.get(className.replaceAll("/", "."));
if (s != null) {
if (s.startsWith("http")) {
return readUrlFile(s);
} else {
return getBytesFromFile(s);
}
}
return null;
}
public static byte[] getBytesFromFile(String fileName) {
File file = new File(fileName);
try (InputStream is = new FileInputStream(file)) {
// precondition
long length = file.length();
byte[] bytes = new byte[(int) length];
// Read in the bytes
int offset = 0;
int numRead = 0;
while (offset < bytes.length
&& (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
offset += numRead;
}
if (offset < bytes.length) {
throw new IOException("Could not completely read file "
+ file.getName());
}
is.close();
return bytes;
} catch (Exception e) {
System.out.println("error occurs in _ClassTransformer!"
+ e.getClass().getName());
return null;
}
}
public static byte[] readUrlFile(String strUrl) {
try {
// 统一资源
URL url = new URL(strUrl);
// 连接类的父类,抽象类
URLConnection urlConnection = url.openConnection();
// http的连接类
HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;
//设置超时
httpURLConnection.setConnectTimeout(1000 * 5);
//设置请求方式,默认是GET
httpURLConnection.setRequestMethod("POST");
// 设置字符编码
httpURLConnection.setRequestProperty("Charset", "UTF-8");
// 打开到此 URL引用的资源的通信链接(如果尚未建立这样的连接)。
httpURLConnection.connect();
// 建立链接从请求中获取数据
URLConnection con = url.openConnection();
BufferedInputStream bin = new BufferedInputStream(con.getInputStream());
ByteArrayOutputStream out = new ByteArrayOutputStream();
int size;
byte[] buf = new byte[2048];
while ((size = bin.read(buf)) != -1) {
out.write(buf, 0, size);
}
// 关闭资源
bin.close();
byte[] bytes = out.toByteArray();
out.close();
return bytes;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
byte[] bytes = readUrlFile("http://139.9.87.17:30020/file/temp/4d36413c-3de4-4075-859b-fdeb9ebcf1b7.class");
System.out.println(bytes.length);
}
}
本地:
rancher日志:
一句话就是,聊胜于无。但是对我而言却是是有一点点帮助,有时候代码有点小错误还是可以直接改的