Java探针参考:Java探针技术在应用安全领域的新突破java
最近面试阿里,面试官先是问我类加载的流程,而后问了个问题,可否在加载类的时候,对字节码进行修改linux
我懵逼了,答曰不知道,面试官说能够的,使用Java探针技术,可以实现git
我查了一下关于探针技术的知识:github
2. 基于javaAgent和Java字节码注入技术的java探针工具技术原理web
图0-0:动态代理功能实现说明面试
咱们利用javaAgent和ASM字节码技术开发java探针工具,实现原理以下:spring
jdk1.5之后引入了javaAgent技术,javaAgent是运行方法以前的拦截器。咱们利用javaAgent和ASM字节码技术,在JVM加载class二进制文件的时候,利用ASM动态的修改加载的class文件,在监控的方法先后添加计时器功能,用于计算监控方法耗时,同时将方法耗时及内部调用状况放入处理器,处理器利用栈先进后出的特色对方法调用前后顺序作处理,当一个请求处理结束后,将耗时方法轨迹和入参map输出到文件中,而后根据map中相应参数或耗时方法轨迹中的关键代码区分出咱们要抓取的耗时业务。最后将相应耗时轨迹文件取下来,转化为xml格式并进行解析,经过浏览器将代码分层结构展现出来,方便耗时分析,如图0-1所示。windows
图0-1:java探针工具原理图浏览器
Java探针工具功能点:安全
一、支持方法执行耗时范围抓取设置,根据耗时范围抓取系统运行时出如今设置耗时范围的代码运行轨迹。
二、支持抓取特定的代码配置,方便对配置的特定方法进行抓取,过滤出关系的代码执行耗时状况。
三、支持APP层入口方法过滤,配置入口运行前的方法进行监控,至关于监控特有的方法耗时,进行方法专题分析。
四、支持入口方法参数输出功能,方便跟踪耗时高的时候对应的入参数。
五、提供WEB页面展现接口耗时展现、代码调用关系图展现、方法耗时百分比展现、可疑方法凸显功能。
下面看个例子:
第一篇:
JavaAgent 是JDK 1.5 之后引入的,也能够叫作Java代理。
JavaAgent 是运行在 main方法以前的拦截器,它内定的方法名叫 premain ,也就是说先执行 premain 方法而后再执行 main 方法。
那么如何实现一个 JavaAgent 呢?很简单,只须要增长 premain 方法便可。
看下面的代码和代码中的注释说明:
先写一个premain方法:
package agent; import java.lang.instrument.Instrumentation; public class pre_MyProgram { /** * 该方法在main方法以前运行,与main方法运行在同一个JVM中 * 并被同一个System ClassLoader装载 * 被统一的安全策略(security policy)和上下文(context)管理 * * @param agentOps * @param inst * @author SHANHY * @create 2016年3月30日 */ public static void premain(String agentOps,Instrumentation inst){ System.out.println("====premain 方法执行"); System.out.println(agentOps); } /** * 若是不存在 premain(String agentOps, Instrumentation inst) * 则会执行 premain(String agentOps) * * @param agentOps * @author SHANHY * @create 2016年3月30日 */ public static void premain(String agentOps){ System.out.println("====premain方法执行2===="); System.out.println(agentOps); } public static void main(String[] args) { // TODO Auto-generated method stub } }
写完这个类后,咱们还须要作一步配置工做。
在 src 目录下添加 META-INF/MANIFEST.MF 文件,内容按以下定义:
Manifest-Version: 1.0
Premain-Class: agent.pre_MyProgram
Can-Redefine-Classes: true
要特别注意,一共是四行,第四行是空行,还有就是冒号后面的一个空格,以下截图:
而后咱们打包代码为 pre_MyProgram.jar
注意打包的时候选择咱们本身定义的 MANIFEST.MF ,这是导出步骤:
(1)
(2) 注意选择pre的MF文件
接着咱们在建立一个带有main方法的主程序工程,截图以下:
这时候别忘了:
main函数也有MF文件:别写错了,否则导出报错:No main manifest attribute(说明MF文件写错了)
Manifest-Version: 1.0
Main-Class: alibaba.MyProgram
按一样的方法导出main的jar包命名为:MyProgram.jar
以下:
选择它的MF文件:
如何执行 MyProgram.jar ?咱们经过 -javaagent 参数来指定咱们的Java代理包,值得一说的是 -javaagent 这个参数的个数是不限的,若是指定了多个,则会按指定的前后执行,执行完各个 agent 后,才会执行主程序的 main 方法。
命令以下:
C:\WINDOWS\system32>java -javaagent:C:\Users\z003fe9c\Desktop\tessdata\agent\pre
_MyProgram.jar=Hello1 -javaagent:C:\Users\z003fe9c\Desktop\tessdata\agent\pre_My
Program.jar=Hello2 -jar C:\Users\z003fe9c\Desktop\tessdata\agent\MyProgram.jar
输出结果:
====premain 方法执行 Hello1 ====premain 方法执行 Hello2 =========main方法执行====
特别提醒:
(1)若是你把 -javaagent 放在 -jar 后面,则不会生效。也就是说,放在主程序后面的 agent 是无效的。
好比执行:
java -javaagent:G:\myagent.jar=Hello1 -javaagent:G:\myagent.jar=Hello2 -jar myapp.jar -javaagent:G:\myagent.jar=Hello3
(2)若是main函数忘了选择MF文件或是MF文件选择的不对,就会报错:
只会有前个生效,第三个是无效的。
命令中的Hello1为咱们传递给 premain 方法的字符串参数。
至此,咱们会使用 javaagent 了,可是单单看这样运行的效果,好像没有什么实际意义嘛。
咱们能够用 javaagent 作什么呢?下篇文章咱们来介绍如何在项目中应用 javaagent。
最后说一下,还有一种,在main方法执行后再执行代理的方法,由于不经常使用,并且主程序须要配置 Agent-Class,因此不经常使用,若是须要自行了解下 agentmain(String agentArgs, Instrumentation inst) 方法。
第二篇:
今后处开始,到最后,是我直接复制了其余人员的,由于我本身的一直没有调试出来,不过思路清楚了:
第二篇能够直接看别人的 JavaAgent 应用(spring-loaded 热部署),如下的能够忽略掉:
上一篇文章简单介绍了 javaagent ,想了解的能够移步 “JavaAgent”
本文重点说一下,JavaAgent 能给咱们带来什么?
JDK5中只能经过命令行参数在启动JVM时指定javaagent参数来设置代理类,而JDK6中已经不只限于在启动JVM时经过配置参数来设置代理类,JDK6中经过 Java Tool API 中的 attach 方式,咱们也能够很方便地在运行过程当中动态地设置加载代理类,以达到 instrumentation 的目的。
Instrumentation 的最大做用,就是类定义动态改变和操做。
最简单的一个例子,计算某个方法执行须要的时间,不修改源代码的方式,使用Instrumentation 代理来实现这个功能,给力的说,这种方式至关于在JVM级别作了AOP支持,这样咱们能够在不修改应用程序的基础上就作到了AOP,是否是显得略吊。
接着上一篇文章的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; } }
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(); } } }
修改MyAgent.java 的 permain 方法,以下:
public static void premain(String agentOps, Instrumentation inst) { System.out.println("=========premain方法执行========"); System.out.println(agentOps); // 添加Transformer inst.addTransformer(new MyTransformer()); }
修改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
对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.
在项目开发中咱们能够把一些重要但又可能会变动的逻辑封装到某个 logic.jar 中,当咱们须要随时更新实现逻辑的时候,能够在不重启服务的状况下让修改后的 logic.jar 被从新加载生效。
spring-loaded是一个开源项目,项目地址:https://github.com/spring-projects/spring-loaded
使用方法:
在启动主程序以前指定参数
在启动主程序以前指定参数
-javaagent:C:/springloaded-1.2.5.RELEASE.jar -noverify
若是你想让 Tomat 下面的应用自动热部署,只须要在 catalina.sh 中添加:
set JAVA_OPTS=-javaagent:springloaded-1.2.5.RELEASE.jar -noverify
这样就完成了 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的配置文件的修改不能即时生效。