本地debug的时候,可以实时编译并更新代码,线上也可以不停服来动态更新类,即所说的java热部署。
JDK代理的两种方式:
1.premain方式是Java SE5开始就提供的代理方式,但其必须在命令行指定代理jar,并且代理类必须在main方法前启动,它要求开发者在应用启动前就必须确认代理的处理逻辑和参数内容等等
2.agentmain方式是JavaSE6开始提供,它可以在应用程序的VM启动后再动态添加代理的方式
agentmain应用场景:
比如正常的生产环境下,一般不会开启代理功能,但是在发生问题时,我们不希望停止应用就能够动态的去修改一些类的行为,以帮助排查问题,这在应用启动前是无法确定的
与Permain类似,agent方式同样需要提供一个agent jar,并且这个jar需要满足[可查看附件的jar文件]:
1.在manifest中指定Agent-Class属性,值为代理类全路径
2.代理类需要提供public static void agentmain(String args, Instrumentation inst)或public static void agentmain(String args)方法。并且再二者同时存在时以前者优先。args和inst和premain中的一致。
那如何在不停服的情况下动态修改类呢??
实现代码如下:
Java Code 代码动态更改类
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
public class JavaAgent {
public static final Logger logger = LoggerFactory.getLogger(JavaAgent.class); private static String classesPath; private static String jarPath; private static VirtualMachine vm; private static String pid; static { classesPath = JavaAgent.class.getClassLoader().getResource("").getPath(); logger.error("java agent:classpath:{}", classesPath); jarPath = getJarPath(); logger.error("java agent:jarPath:{}", jarPath); // 当前进程pid String name = ManagementFactory.getRuntimeMXBean().getName(); pid = name.split("@")[0]; logger.error("当前进程pid:{}", pid); } /** * 获取jar包路径 * @return */ public static String getJarPath() { // StringUtils是jar文件内容 URL url = StringUtils.class.getProtectionDomain().getCodeSource().getLocation(); String filePath = null; try { filePath = URLDecoder.decode(url.getPath(), "utf-8");// 转化为utf-8编码 } catch (Exception e) { e.printStackTrace(); } if (filePath.endsWith(".jar")) {// 可执行jar包运行的结果里包含".jar" // 截取路径中的jar包名 filePath = filePath.substring(0, filePath.lastIndexOf("/") + 1); } File file = new File(filePath); filePath = file.getAbsolutePath();//得到windows下的正确路径 return filePath; } private static void init() throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException { // 虚拟机加载 vm = VirtualMachine.attach(pid); vm.loadAgent(jarPath + "/javaagent.jar"); Instrumentation instrumentation = JavaDynAgent.getInstrumentation(); Preconditions.checkNotNull(instrumentation, "initInstrumentation must not be null"); } private static void destroy() throws IOException { if (vm != null) { vm.detach(); } } /** * 重新加载类 * * @param classArr * @throws Exception */ public static void javaAgent(String root, String[] classArr) throws ClassNotFoundException, IOException, UnmodifiableClassException, AttachNotSupportedException, AgentLoadException, AgentInitializationException { init(); try { // 1.整理需要重定义的类 List<ClassDefinition> classDefList = new ArrayList<ClassDefinition>(); for (String className : classArr) { Class<?> c = Class.forName(className); String classPath = (StringUtils.isNotBlank(root) ? root : classesPath) + className.replace(".", "/") + ".class"; logger.error("class redefined:" + classPath); byte[] bytesFromFile = Files.toByteArray(new File(classPath)); ClassDefinition classDefinition = new ClassDefinition(c, bytesFromFile); classDefList.add(classDefinition); } // 2.redefine JavaDynAgent.getInstrumentation().redefineClasses(classDefList.toArray(new ClassDefinition[classDefList.size()])); } finally { destroy(); } } public static void main(String[] args) throws Exception { PortUtil.test(); javaAgent(null, new String[] {"com.agileeagle.webgame.framework.util.PortUtil"}); PortUtil.test(); } } |
Java Code Instrumentation如何获取?
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class JavaDynAgent {
private static Instrumentation instrumentation; private static Object lockObject = new Object(); public JavaDynAgent() { } public static void agentmain(String args, Instrumentation inst) { Object var2 = lockObject; synchronized(lockObject) { if(instrumentation == null) { instrumentation = inst; System.out.println("0->" + inst); } else { System.out.println("1->" + inst); } } } public static Instrumentation getInstrumentation() { return instrumentation; } } |
实现原理是:
1.绑定pid获得虚拟机对象,然后通过虚拟机加载代理jar包,这样就调用到agentmain,获取得到Instrumentation
2.基于Instrumentation接口可以实现JDK的代理机制,从而实现对类进行动态重新定义。
注意:com.sun.tools.attach.VirtualMachine的jar包是 jdk下lib中的tools.jar,所以项目中要引用到这个jar包,而且因为涉及到底层虚拟机,windows和linux机器这个jar不同
因此,整个流程就是:
1.项目中引用 jdk/lib/tools.jar,否则无法使用VirtualMachine类
2.项目中引用 javaagent.jar ,它提供了agentmain接口
3.代码实现动态增加JDK代理
新的原理解析说明:Java线上解决方案1:JAVA热更新
测试代码github:示例代码github