JAVA代码热部署,在线不停服动态更新

本地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代理

时间: 2024-10-06 13:46:55

JAVA代码热部署,在线不停服动态更新的相关文章

springboot 不停服动态更新定时任务时间(转)

转 https://blog.csdn.net/u012129558/article/details/80834303 Spring框架自3.0版本起,自带了任务调度功能,好比是一个轻量级的Quartz,而且使用起来也方便.简单,且不需要依赖其他的JAR包.秉承着Spring的一贯风格,Spring任务调度的实现同时支持注解配置和XML配置两种方式. 再来谈谈变态的项目需求:我们正在做一个智能数字电表的数据采集项目,项目最终会在多个工业园上线,每个工业园对电表数据的采集周期可以进行自定义,例如A

基于Instrumentation的JAVA代码热替换

理类用来获取 Instrumentation 实例 package com.codeconch.util; import java.lang.instrument.Instrumentation; public class Monitor { private static Instrumentation instrumentation; public static void premain(String args, Instrumentation inst) { instrumentation

java的热部署和热加载

ps:热部署和热加载其实是两个类似但不同的概念,之前理解不深,so,这篇文章重构了下. 一.热部署与热加载 在应用运行的时升级软件,无需重新启动的方式有两种,热部署和热加载. 对于Java应用程序来说,热部署就是在服务器运行时重新部署项目,热加载即在在运行时重新加载class,从而升级应用. 二.实现原理 热加载的实现原理主要依赖java的类加载机制,在实现方式可以概括为在容器启动的时候起一条后台线程,定时的检测类文件的时间戳变化,如果类的时间戳变掉了,则将类重新载入. 对比反射机制,反射是在运

Java服务器热部署的实现原理

转自:http://blog.csdn.net/chenjie19891104/article/details/42807959 在web应用开发或者游戏服务器开发的过程中,我们时时刻刻都在使用热部署.热部署的目的很简单,就是为了节省应用开发和发布的时间.比如,我们在使用Tomcat或者Jboss等应用服务器开发应用时,我们经常会开启热部署功能.热部署,简单点来说,就是我们将打包好的应用直接替换掉原有的应用,不用关闭或者重启服务器,一切就是这么简单.那么,热部署到底是如何实现的呢?在本文中,我将

java 中 热部署与卸载关系

今天发现早年在大象笔记中写的一篇笔记,之前放在ijavaboy上的,现在它已经访问不了了.前几天又有同事在讨论这个问题.这里拿来分享一下. 在web应用开发或者游戏服务器开发的过程中,我们时时刻刻都在使用热部署.热部署的目的很简单,就是为了节省应用开发和发布的时间.比如,我们在使用Tomcat或者Jboss等应用服务器开发应用时,我们经常会开启热部署功能.热部署,简单点来说,就是我们将打包好的应用直接替换掉原有的应用,不用关闭或者重启服务器,一切就是这么简单.那么,热部署到底是如何实现的呢?在本

Java 项目热部署,节省构建时间的正确姿势

上周末,帮杨小邪(我的大学室友)远程调试项目.SpringBoot 构建,没有热部署,改一下就得重启相关模块.小小的 bug ,搞了我一个多小时,大部分时间都还在构建上(特么,下次得收钱才行).我跟他说有个热部署插件叫 Jrebel,可以热部署,每次修改完代码需要测试的时候,只要 Build 一下就行?所有 Java 项目都能使用,他居然不知道.作为一个英俊男孩,我不得不写下这篇文章教他使用热部署. Jrebel 是什么? JRebel 是一款 JAVA 虚拟机插件,它使得 JAVA 程序员能在

探秘 Java 热部署三(Java agent agentmain)

前言 让我们继续探秘 Java 热部署.在前文 探秘 Java 热部署二(Java agent premain)中,我们介绍了 Java agent premain.通过在main方法之前通过类似 AOP 的方式添加 premain 方法,我们可以在类加载之前做修改字节码的操作,无论是第一次加载,还是每次新的 ClassLoader 加载,都会经过 ClassFileTransformer 的 transform 方法,也就是说,都可以在这个方法中修改字节码,虽然他的方法名是 premain ,

JAVA热部署原理

1.热部署是什么? 对于Java应用程序来说,热部署就是在运行时更新Java类文件. 2.热部署有什么用? 可以不重启应用的情况下,更新应用.举个例子,就像电脑可以在不重启的情况下,更换U盘. OSGI也正是因为它的模块化和热部署,才显得热门. 3.热部署的原理是什么? 想要知道热部署的原理,必须要了解java类的加载过程.一个java类文件到虚拟机里的对象,要经过如下过程. 首先通过java编译器,将java文件编译成class字节码,类加载器读取class字节码,再将类转化为实例,对实例ne

Tomcat热部署的实现原理

概述 名词解释:所谓热部署,就是在应用正在运行的时候升级软件,却不需要重新启动应用. 对于Java应用程序来说,热部署就是在运行时更新Java类文件.在基于Java的应用服务器实现热部署的过程中,类装入器扮演着重要的角色.大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,都支持热部署.类装入器不能重新装入一个已经装入的类,但只要使用一个新的类装入器实例,就可以将类再次装入一个正在运行的应用程序. 我们知道,现在大多数的web服务器都支持热部署,而对于热部署的实现机制,网上讲的