JavaAgent 应用(spring-loaded 热部署)
上一篇文章简单介绍了 javaagent ,想了解的可以移步 “JavaAgent”
本文重点说一下,JavaAgent 能给我们带来什么?
- 自己实现一个 JavaAgent xxxxxx
- 基于 JavaAgent 的 spring-loaded 实现 jar 包的热更新,也就是在不重启服务器的情况下,使我们某个更新的 jar 被重新加载。
一、基于 JavaAgent 的应用实例
JDK5中只能通过命令行参数在启动JVM时指定javaagent参数来设置代理类,而JDK6中已经不仅限于在启动JVM时通过配置参数来设置代理类,JDK6中通过 JavaTool 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
- 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
- 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
- 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
- 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
- 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
- 1
- 2
- 3
如果你想让 Tomat 下面的应用自动热部署,只需要在 catalina.sh 中添加:
set JAVA_OPTS=-javaagent:springloaded-1.2.5.RELEASE.jar -noverify
- 1
- 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的配置文件的修改不能即时生效。
JavaAgent 应用(spring-loaded 热部署)的更多相关文章
- spring boot: 热部署(一) run as – java application (spring-loader-1.2.4.RELEASE.jar)
spring boot: 热部署(一) run as – java application (spring-loader-1.2.4.RELEASE.jar) 如果使用的run as – java a ...
- Spring Boot 系列(六)web开发-Spring Boot 热部署
Spring Boot 热部署 实际开发中,修改某个页面数据或逻辑功能都需要重启应用.这无形中降低了开发效率,所以使用热部署是十分必要的. 什么是热部署? 应用启动后会把编译好的Class文件加载的虚 ...
- spring boot: 热部署spring-boot-devtools
spring boot: 热部署spring-boot-devtools 1引入spring-boot-devtools依赖包 <!-- spring boot devtools 热部署 --& ...
- Spring Boot 热部署(转)
Spring Boot 热部署 实际开发中,修改某个页面数据或逻辑功能都需要重启应用.这无形中降低了开发效率,所以使用热部署是十分必要的. 什么是热部署? 应用启动后会把编译好的Class文件加载的虚 ...
- Spring Boot热部署插件
在实际开发中,我们修改某些代码逻辑功能或页面都需要重启应用,这无形中降低了开发效率,热部署是指当我们修改代码后,服务能自动重启加载新修改的内容,而不需要重启应用,这样大大提高了我们开发的效率. Spr ...
- 3,Spring Boot热部署
问题的提出: 在编写代码的时候,你会发现我们只是简单把打印信息改变了,就需要重新部署,如果是这样的编码方式,那么我们估计一天下来就真的是打几个Hello World就下班了.那么如何解决热部署的问题呢 ...
- spring boot 热部署,省去频繁编译的步骤
一.热启动: 每自修改后, 程序自动启动Spring Application上下文. Pom中直接添加依赖即可: <dependency> <groupId&g ...
- spring boot热部署pom.xml配置
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...
- Spring Boot 热部署的实现 - 原创
实现方式有两大种(其中包含3种): 一.基于springloaded 1.1)Maven启动方式 第一步:在pom.xml中的“plugin节点”里面添加如下依赖: <dependencies& ...
随机推荐
- UNIX环境编程学习笔记(16)——进程管理之进程环境变量
lienhua342014-10-03 1 环境表和环境指针 在每个进程启动时,都会接到一张环境表.环境表是一个字符指针数组,其中每个指针包含一个以 null 结束的 C 字符串的地址.全局变量env ...
- jenkins 神奇变量
Hudson自己设置的一些环境变量可用于通过Hudson来执行shell脚本.Windows批处理文件和Ant文件,他们包括 Hudson设置环境变量 当一个Hudson作业执行时,它会设置一些环境变 ...
- ABBYY FineReader 12中的用户模式你会用吗
在ABBYY FineReader 12OCR文字识别软件中,有一个概念叫“训练”,它是在字符图像和字符本身之间建立对应关系的过程,训练模式可以提高含有装饰字体的文档或包含特殊字符(例如数学符号)文档 ...
- 怎么让BarTender对象等间距分布
在BarTender 2016设计条码标签时,我们需要让对象分布尽可能整齐美观,例如实现对象的对齐,对象等间距分布等.这些在作为世界上最好且最受信任的条码打印软件BarTender中,都是可以很轻松的 ...
- linux下安装软件的常用方法
在使用Linux系统的过程中,软件包的安装是避免不了的,在Linux下,软件安装程序的种类很多,安装方法也各式各样,(舒适性自然比不上windows :-))不过我们常见的软件包有两种: 1)含有软件 ...
- 利用ROS工具从bag文件中提取图片
bag文件是ROS常用的数据存储格式,因此要从bag文件中提取数据就需要了解一点ROS的背景知识. 1. 什么是ROS及其优势 ROS全称Robot Operating System,是BSD-lic ...
- phpcms v9 get的强大之处(列表页调用点击数)
{pc:get sql="select * from v9_art as g left join v9_art_data as p on p.id=g.id and g.catid=12 o ...
- CDbConnection failed to open the DB connection: could not find driver错误的处理
在PHP.INI文件中extension=php_pdo_mysql.dll 去掉注释
- js 或 且 非
给定 x=6 以及 y=3,下表解释了逻辑运算符: 运算符 描述 例子 && and (x < 10 && y > 1) 为 true || or (x== ...
- IIS------项目配置到IIS后报500错误
转载: http://blog.csdn.net/yinjingjing198808/article/details/7185453 错误一: HTTP Error 500.19 - Internal ...