本文共 7540 字,大约阅读时间需要 25 分钟。
Java Agent提供了一种在加载字节码时,对字节码进行修改的方法。一共有两种方式执行:一种是在main方法执行之前,通过premain来实现;另一种是在程序运行中,通过attach api来实现
Instrumentation是JDK1.5提供的API,用于拦截类加载事件,并对字节码进行修改,它的主要方法如下:
public interface Instrumentation { //注册一个转换器,类加载事件会被注册的转换器所拦截 void addTransformer(ClassFileTransformer transformer, boolean canRetransform); //重新触发类加载 void retransformClasses(Class ... classes) throws UnmodifiableClassException; //直接替换类的定义 void redefineClasses(ClassDefinition... definitions) throws ClassNotFoundException, UnmodifiableClassException;
premain()
方法premain()
方法是在main()
方法之前运行的方法,运行时需要将agent程序打包成jar包,并在启动时添加命令来执行:
-javaagent:[= ]
premain()
共提供以下两种重载方法,JVM启动时会先尝试使用第一种方法,若没有会使用第二种方法
public static void premain(String agentArgs, Instrumentation inst) public static void premain(String agentArgs)
Byte Buddy是一个代码生成和操作库,用于在Java应用程序运行时创建和修改Java类,而无需编译器的帮助。除了Java类库附带的代码生成实用程序外,Byte Buddy还允许创建任意类,并且不限于实现用于创建运行时代理的接口。此外,Byte Buddy提供了一种方便的API,可以使用Java代理或在构建过程中手动更改类
pom.xml中引入Byte Buddy相关依赖,并指定MANIFEST.MF文件路径
4.0.0 com.ppdai bytebuddy-agent-demo 1.0-SNAPSHOT net.bytebuddy byte-buddy 1.8.20 net.bytebuddy byte-buddy-agent 1.8.20 org.apache.maven.plugins maven-compiler-plugin org.apache.maven.plugins maven-jar-plugin src/main/resources/META-INF/MANIFEST.MF true
MethodCostTime:
public class MethodCostTime { @RuntimeType public static Object intercept(@Origin Method method, @SuperCall Callable callable) throws Exception { long start = System.currentTimeMillis(); try { //原有函数执行 return callable.call(); } finally { System.out.println(method + " 方法耗时: " + (System.currentTimeMillis() - start) + "ms"); } }}
MyAgent:
public class MyAgent { public static void premain(String agentArgs, Instrumentation inst) { System.out.println("MyAgent init..."); AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> builder //拦截任意方法 .method(ElementMatchers.any()) //委托 .intercept(MethodDelegation.to(MethodCostTime.class)); AgentBuilder.Listener listener = new AgentBuilder.Listener() { @Override public void onDiscovery(String s, ClassLoader classLoader, JavaModule javaModule, boolean b) { } @Override public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule, boolean b, DynamicType dynamicType) { } @Override public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule, boolean b) { } @Override public void onError(String s, ClassLoader classLoader, JavaModule javaModule, boolean b, Throwable throwable) { } @Override public void onComplete(String s, ClassLoader classLoader, JavaModule javaModule, boolean b) { } }; new AgentBuilder .Default() //指定需要拦截的类 .type(ElementMatchers.nameStartsWith("com.ppdai")) .transform(transformer) .with(listener) .installOn(inst); }}
在src/main/resources
目录下添加META-INF/MANIFEST.MF
文件,内容如下:
Manifest-Version: 1.0Premain-Class: com.ppdai.agent.MyAgentCan-Redefine-Classes: true
使用mvn clean package
打包
测试类:
public class AgentTest { public static void main(String[] args) throws InterruptedException { Thread.sleep(new Random().nextInt(500)); }}
运行测试类时需要指定agent的jar包路径
-javaagent:[= ]
运行结果:
MyAgent init...public static void com.ppdai.test.AgentTest.main(java.lang.String[]) throws java.lang.InterruptedException 方法耗时: 350ms
源码已上传GitHub:https://github.com/hxt970311/bytebuddy-agent-demo
AspectJ作为AOP编程的完全解决方案,提供了三种织入时机,分别为:
引入aspectj相关依赖:
org.aspectj aspectjrt 1.8.13
编写Aspect:
@Aspectpublic class MethodCostTimeAspect { @Pointcut("execution(* com.ppdai..*(..))") public void pointcut() { } @Around("pointcut()") public Object logProfile(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); Object result = joinPoint.proceed(); System.out.println(joinPoint.getSignature() + " 方法耗时: " + (System.currentTimeMillis() - start) + "ms"); return result; }}
在src/main/resources
目录下添加META-INF/aop.xml
文件,指定Aspect类和需要被织入的类,内容如下:
测试类:
public class AspectjLtwTest { public static void main(String[] args) throws InterruptedException { Thread.sleep(new Random().nextInt(500)); }}
运行测试类时指定agent aspectjweaver的jar包路径
-javaagent:/Users/hanxiantao/IdeaProjects/aspectj-ltw/target/aspectjweaver-1.8.13.jar
通过引入aspectjweaver依赖,到本地maven仓库获取aspectjweaver-1.8.13.jar
org.aspectj aspectjweaver 1.8.13
运行结果:
[AppClassLoader@18b4aac2] info AspectJ Weaver Version 1.8.13 built on Wednesday Nov 15, 2017 at 19:26:44 GMT[AppClassLoader@18b4aac2] info register classloader sun.misc.Launcher$AppClassLoader@18b4aac2[AppClassLoader@18b4aac2] info using configuration /Users/hanxiantao/IdeaProjects/aspectj-ltw/target/classes/META-INF/aop.xml[AppClassLoader@18b4aac2] info register aspect com.ppdai.MethodCostTimeAspect[AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(void com.ppdai.AspectjLtwTest.main(java.lang.String[]))' in Type 'com.ppdai.AspectjLtwTest' (AspectjLtwTest.java:12) advised by around advice from 'com.ppdai.MethodCostTimeAspect' (MethodCostTimeAspect.java)[AppClassLoader@18b4aac2] weaveinfo Join point 'method-execution(void com.ppdai.MethodCostTimeAspect.pointcut())' in Type 'com.ppdai.MethodCostTimeAspect' (MethodCostTimeAspect.java:17) advised by around advice from 'com.ppdai.MethodCostTimeAspect' (MethodCostTimeAspect.java)void com.ppdai.AspectjLtwTest.main(String[]) 方法耗时: 428ms
源码已上传GitHub:https://github.com/hxt970311/aspectj-ltw
参考:
https://mp.weixin.qq.com/s?__biz=MzI3NzE0NjcwMg==&mid=2650130323&idx=3&sn=f22cf468dd7a85539c5ab40cee8bb9ef
https://bugstack.blog.csdn.net/article/details/100044939
https://blog.csdn.net/generalfu/article/details/106086475
https://www.javadoop.com/post/aspectj#Load-Time%20Weaving