`
shenhaiyue
  • 浏览: 25889 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

JavaAgent 应用(spring-loaded 热部署)

    博客分类:
  • java
 
阅读更多
上一篇文章简单介绍了 javaagent ,想了解的可以移步 “JavaAgent”

本文重点说一下,JavaAgent 能给我们带来什么?

自己实现一个 JavaAgent xxxxxx
基于 JavaAgent 的 spring-loaded 实现 jar 包的热更新,也就是在不重启服务器的情况下,使我们某个更新的 jar 被重新加载。
一、基于 JavaAgent 的应用实例
JDK5中只能通过命令行参数在启动JVM时指定javaagent参数来设置代理类,而JDK6中已经不仅限于在启动JVM时通过配置参数来设置代理类,JDK6中通过 Java Tool API 中的 attach 方式,我们也可以很方便地在运行过程中动态地设置加载代理类,以达到 instrumentation 的目的。
Instrumentation 的最大作用,就是类定义动态改变和操作。

最简单的一个例子,计算某个方法执行需要的时间,不修改源代码的方式,使用Instrumentation 代理来实现这个功能,给力的说,这种方式相当于在JVM级别做了AOP支持,这样我们可以在不修改应用程序的基础上就做到了AOP,是不是显得略吊。

创建一个 ClassFileTransformer 接口的实现类 MyTransformer
实现 ClassFileTransformer 这个接口的目的就是在class被装载到JVM之前将class字节码转换掉,从而达到动态注入代码的目的。那么首先要了解MonitorTransformer 这个类的目的,就是对想要修改的类做一次转换,这个用到了javassist对字节码进行修改,可以暂时不用关心jaavssist的原理,用ASM同样可以修改字节码,只不过比较麻烦些。
接着上一篇文章的2个工程,分别添加下面的类。
MyTransformer.java 添加到 MyAgent 工程中。

package com.shanhy.demo.agent;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;

/**
* 检测方法的执行时间
*
* @author 单红宇(365384722)
* @myblog http://blog.csdn.net/catoop/
* @create 2016年3月30日
*/
public class MyTransformer implements ClassFileTransformer {

    final static String prefix = "\nlong startTime = System.currentTimeMillis();\n";
    final static String postfix = "\nlong endTime = System.currentTimeMillis();\n";

    // 被处理的方法列表
    final static Map<String, List<String>> methodMap = new HashMap<String, List<String>>();

    public MyTransformer() {
        add("com.shanhy.demo.TimeTest.sayHello");
        add("com.shanhy.demo.TimeTest.sayHello2");
    }

    private void add(String methodString) {
        String className = methodString.substring(0, methodString.lastIndexOf("."));
        String methodName = methodString.substring(methodString.lastIndexOf(".") + 1);
        List<String> list = methodMap.get(className);
        if (list == null) {
            list = new ArrayList<String>();
            methodMap.put(className, list);
        }
        list.add(methodName);
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        className = className.replace("/", ".");
        if (methodMap.containsKey(className)) {// 判断加载的class的包路径是不是需要监控的类
            CtClass ctclass = null;
            try {
                ctclass = ClassPool.getDefault().get(className);// 使用全称,用于取得字节码类<使用javassist>
                for (String methodName : methodMap.get(className)) {
                    String outputStr = "\nSystem.out.println(\"this method " + methodName
                            + " cost:\" +(endTime - startTime) +\"ms.\");";

                    CtMethod ctmethod = ctclass.getDeclaredMethod(methodName);// 得到这方法实例
                    String newMethodName = methodName + "$old";// 新定义一个方法叫做比如sayHello$old
                    ctmethod.setName(newMethodName);// 将原来的方法名字修改

                    // 创建新的方法,复制原来的方法,名字为原来的名字
                    CtMethod newMethod = CtNewMethod.copy(ctmethod, methodName, ctclass, null);

                    // 构建新的方法体
                    StringBuilder bodyStr = new StringBuilder();
                    bodyStr.append("{");
                    bodyStr.append(prefix);
                    bodyStr.append(newMethodName + "($$);\n");// 调用原有代码,类似于method();($$)表示所有的参数
                    bodyStr.append(postfix);
                    bodyStr.append(outputStr);
                    bodyStr.append("}");

                    newMethod.setBody(bodyStr.toString());// 替换新方法
                    ctclass.addMethod(newMethod);// 增加新方法
                }
                return ctclass.toBytecode();
            } catch (Exception e) {
                System.out.println(e.getMessage());
                e.printStackTrace();
            }
        }
        return null;
    }
}
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
TimeTest.java 添加到 MyProgram 工程中。

package com.shanhy.demo;

/**
* 被测试类
*
* @author   单红宇(365384722)
* @myblog  http://blog.csdn.net/catoop/
* @create    2016年3月30日
*/
public class TimeTest {

    public static void main(String[] args) {
        sayHello();
        sayHello2("hello world222222222");
    }

    public static void sayHello() {
        try {
            Thread.sleep(2000);
            System.out.println("hello world!!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void sayHello2(String hello) {
        try {
            Thread.sleep(1000);
            System.out.println(hello);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
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
修改MyAgent.java 的 permain 方法,如下:

    public static void premain(String agentOps, Instrumentation inst) {
        System.out.println("=========premain方法执行========");
        System.out.println(agentOps);
        // 添加Transformer
        inst.addTransformer(new MyTransformer());
    }
1
2
3
4
5
6
修改MANIFEST.MF内容,增加 Boot-Class-Path 如下:

Manifest-Version: 1.0
Premain-Class: com.shanhy.demo.agent.MyAgent
Can-Redefine-Classes: true
Boot-Class-Path: javassist-3.18.1-GA.jar
1
2
3
4
5
对2个工程分别打包为 myagent.jar 和 myapp.jar 然后将 javassist-3.18.1-GA.jar 和 myagent.jar 放在一起。

最后执行命令测试,结果如下:

G:\>java -javaagent:G:\myagent.jar=Hello1 -jar myapp.jar
=========premain方法执行========
Hello1
hello world!!
this method sayHello cost:2000ms.
hello world222222222
this method sayHello2 cost:1000ms.
1
2
3
4
5
6
7
二、使用 spring-loaded 实现 jar 包热部署
在项目开发中我们可以把一些重要但又可能会变更的逻辑封装到某个 logic.jar 中,当我们需要随时更新实现逻辑的时候,可以在不重启服务的情况下让修改后的 logic.jar 被重新加载生效。

spring-loaded是一个开源项目,项目地址:https://github.com/spring-projects/spring-loaded

使用方法:

在启动主程序之前指定参数
-javaagent:C:/springloaded-1.2.5.RELEASE.jar -noverify
1
2
3
如果你想让 Tomat 下面的应用自动热部署,只需要在 catalina.sh 中添加:

set JAVA_OPTS=-javaagent:springloaded-1.2.5.RELEASE.jar -noverify
1
这样就完成了 spring-loaded 的安装,它能够自动检测Tomcat 下部署的webapps ,在不重启Tomcat的情况下,实现应用的热部署。

通过使用 -noverify 参数,关闭 Java 字节码的校验功能。
使用参数 -Dspringloaded=verbose;explain;watchJars=tools.jar 指定监视的jar (verbose;explain; 非必须),多个jar用“冒号”分隔,如 watchJars=tools.jar:utils.jar:commons.jar

当然,它也有一些小缺限:
1. 目前官方提供的1.2.4 版本在linux上可以很好的运行,但在windows还存在bug,官网已经有人提出:https://github.com/spring-projects/spring-loaded/issues/145
2. 对于一些第三方框架的注解的修改,不能自动加载,比如:spring mvc的@RequestMapping
3. log4j的配置文件的修改不能即时生效。
————————————————
版权声明:本文为CSDN博主「catoop」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/catoop/article/details/51034778
分享到:
评论

相关推荐

    springloaded-1.2.6实现spring热部署,最全的教程

    SpringLoaded是Spring框架提供的一款强大的热部署工具,它允许开发者在开发过程中无需重启应用程序服务器就能实时看到代码修改的效果。这极大地提高了开发效率,减少了因为频繁重启应用而浪费的时间。本教程将详细...

    springloaded

    SpringLoaded 是一个强大的Java应用程序开发工具,主要用于实现Java程序的热部署功能。热部署是指在程序运行时,当源代码发生改变,无需停止并重新启动应用,就能自动更新已加载的类,使得开发者可以快速看到代码...

    springloaded-1.2.4.RELEASE

    在IDEA或者Eclipse等集成开发环境中,可以通过配置JVM参数来启用SpringLoaded,例如 `-javaagent:path/to/springloaded-x.x.x.jar`。 4. **版本1.2.4.RELEASE** "springloaded-1.2.4.RELEASE"是SpringLoaded的一个...

    springloaded-1.2.4.RELEASE.zip

    - **命令行参数**:在启动JVM时添加`-javaagent`参数,指定SpringLoaded的jar路径,如`-javaagent:path/to/springloaded-1.2.4.RELEASE.jar`。 需要注意的是,虽然SpringLoaded大大简化了开发过程中的部署环节,但...

    springBoot学习笔记整理

    - 下载 `spring-loaded-1.2.4.RELEASE.jar` 文件并放置于项目的 lib 目录下,同时设置 IDEA 的 run 参数中的 VM 参数为 `-javaagent:.\lib\springloaded-1.2.4.RELEASE.jar-noverify`。这样,在使用 `run as Java ...

    2-springboot cloud 28题选择题和2简答题

    1. 热部署方式:可以在 `pom` 文件中加入 `springloaded` 依赖,然后使用 `mvn spring-boot:run` 启动。 2. 也可以使用 `-javaagent` 参数来启动,例如 `-javaagent:'jar 包路径' -noverify`。 六、其他知识点 1. ...

    Springboot在IDEA热部署的配置方法

    通过在启动应用时添加`-javaagent` VM参数指向`springloaded` jar包,如`-javaagent:/path/to/springloaded.jar -noverify`。这种方式对Spring系列框架支持良好,但不适用于Spring Boot项目。 3. 使用`spring-boot-...

    Springboot热部署实现原理及实例详解

    使用 SpringLoaded 本地加载启动,配置 jvm 参数 -javaagent:\包地址&gt; -noverify;使用 DevTools 工具包。其中,第三种热部署方式是使用 DevTools 工具包,因为这种方式操作简单快捷。 在开始配置热部署之前,需要...

    springboot 入门学习笔记

    5. **启用热部署**:使用Spring Loaded项目提供的热部署功能,实现代码修改后即时生效,无需重启服务。只需在`spring-boot-maven-plugin`配置中添加相应依赖。 ```xml &lt;groupId&gt;org.springframework.boot ...

    JAVA热加载eclipse插件

    - 在启动应用服务器时,指定Spring Loaded的系统属性,例如`-XX:MaxPermSize=256m -javaagent:path/to/springloaded-x.x.x.jar`。 - 配置Eclipse项目,使其使用Spring Loaded进行编译和运行。 5. **DCEVM集成** ...

    springboot技术指南

    将springloaded-1.2.4.RELEASE.jar放入项目的lib目录,并在VM arguments中添加`-javaagent:.\lib\springloaded-1.2.4.RELEASE.jar-noverify`。 #### 六、SpringBoot+DevTools实现热部署 ##### 6.1 spring-boot-...

    Springboot指南

    - **方法二**:通过`-javaagent`参数指定`springloaded`的位置。 #### 六、SpringBoot+DevTools实现热部署 ##### 6.1 Spring Boot DevTools简介 Spring Boot DevTools提供了一个开发工具集,可以提高开发者的效率...

Global site tag (gtag.js) - Google Analytics