java Instrument修改字节码实现aop功能
Agent工程2个类:
public class MyAgent { /** * 该方法在main方法之前运行,与main方法运行在同一个JVM中 * 并被同一个System ClassLoader装载 * 被统一的安全策略(security policy)和上下文(context)管理 */ public static void premain(String agentOps, Instrumentation inst) { System.out.println("=========premain方法执行========"); System.out.println(agentOps); // 添加Transformer inst.addTransformer(new MyTransformer()); } /** * 如果不存在 premain(String agentOps, Instrumentation inst) * 则会执行 premain(String agentOps) */ public static void premain(String agentOps) { System.out.println("=========premain方法执行2========"); System.out.println(agentOps); } }
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.hwtest.demo.MyProgram.sayHello"); add("com.hwtest.demo.MyProgram.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; } }
原始项目:
public class MyProgram { 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(); } } }
agent项目打jar包是配置为:
Manifest-Version: 1.0 Premain-Class: com.hwtest.MyAgent Can-Redefine-Classes: true Boot-Class-Path: javassist.jar
cmd执行命令
java -javaagent:MyAgent.jar -jar MyProgram.jar =========premain方法执行======== null hello world!! this method sayHello cost:2000ms. hello world222222222 this method sayHello2 cost:1000ms.
附:agent jar中manifest的属性
- Premain-Class: 当在VM启动时,在命令行中指定代理jar时,必须在manifest中设置Premain-Class属性,值为代理类全类名,并且该代理类必须提供premain方法。否则JVM会异常终止。
- Agent-Class: 当在VM启动之后,动态添加代理jar包时,代理jar包中manifest必须设置Agent-Class属性,值为代理类全类名,并且该代理类必须提供agentmain方法,否则无法启动该代理。
- Boot-Class-Path: Bootstrap class loader加载类时的搜索路径,可选。
- Can-Redefine-Classes: true/false;标示代理类是否能够重定义类。可选。
- Can-Retransform-Classes: true/false;标示代理类是否能够转换类定义。可选。
- Can-Set-Native-Prefix::true/false;标示代理类是否需要本地方法前缀,可选。
当一个代理jar包中的manifest文件中既有Premain-Class又有Agent-Class时,如果以命令行方式在VM启动前指定代理jar,则使用Premain-Class;反之如果在VM启动后,动态添加代理jar,则使用Agent-Class
地址记录:
(1)利用ClassFileTransformer实现aop:http://xj84.iteye.com/blog/1221105
(2)Java通过修改类的字节码实现aop功能:http://www.360doc.com/content/07/0518/11/25392_506401.shtml
(3)java.lang.instrument动态修改替换类代码:http://zctya.blog.163.com/blog/static/1209178201131944127774/