java agent 加载器织入——java.lang.instrument包 AOP,使用javassist
https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247487368&idx=1&sn=408c385d26083803e1a2a742bd301531&chksm=fa497039cd3ef92fcb8274f47130a90c3f9cf11b87b3b73054c20a7693021fb541a27bdf3c8f&mpshare=1&scene=1&srcid=0726C0dbs4R8kkHn1jG1zDK3&sharer_sharetime=1564316461171&sharer_shareid=b3ce2a829d6ac5a5c95b19a19559bab0&key=c1a84807be422668bf1a554d0cdfb3ecd67773641f76efc9a3fa110d4a94c319cf133ab80a19fe139053ae687bcfe6542c40e6f6a9ed8fd931c8aa10d07968a12690c6b87d1f463c027503089471a83c&ascene=0&uin=MTA2NzUxMDAyNQ%3D%3D&devicetype=iMac+MacBookAir6%2C2+OSX+OSX+10.10.5+build(14F2511)&version=11020012&lang=zh_CN&pass_ticket=uyNMv3dkU5lVbMU78UD3%2BXwjTGdkQzT8UBGxvcyQkuNJFvEWu4%2FLP%2Bvxng0jP1ai
二、LTW(Load Time Weaving)
其实,除了运行时织入切面的方式外,我们还有一种途径进行切面织入,它可以在类加载期通过字节码转换,进而将目标织入切入点(目标类),这种方式就是LTW,即静态代理(静待代理也被称作编译时增强,后面会有相关代码样例)。
LTW在Java5的时候就被引入了,想要了解其原理,先要了解一个知识——Instrument包。
由于assembly插件不支持pom中定义premain,故使用menifest
Manifest-Version: 1.0 Premain-Class: agent.MyTransformer Main-Class: agent.MyClient
// 注意这里的空行
放置于resources/META-INF/MANIFEST.MF
pom修改为:
<plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <!--<manifest>--> <!--<mainClass>agent.AgentClient</mainClass>--> <!--</manifest>--> <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile> </archive> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin>
主代码:
import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; /** * https://www.cnblogs.com/silyvin/p/11260965.html * Created by sunyuming on 19/7/28. */ public class MyTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println("类 " + className); return new byte[0]; } public static void premain(String args, Instrumentation instrumentation) { System.out.println("开始premain " + args); ClassFileTransformer classFileTransformer = new MyTransformer(); instrumentation.addTransformer(classFileTransformer); } // cp target/MyTest-1.0-SNAPSHOT-jar-with-dependencies.jar ~/Documents/tool/jars/myagent.jar // java -javaagent:/Users/sunyuming/Documents/tool/jars/myagent.jar=sun -jar target/MyTest-1.0-SNAPSHOT-jar-with-dependencies.jar /** * 不能在此设置main函数,因为MyTransformer这个类会被提前加载,不会打印 */ public static void main(String [] fl) { System.out.println("my client trans"); } }
public class MyClient { public static void main(String [] fl) { System.out.println("my client 6"); } }
运行:
java -javaagent:/Users/sunyuming/Documents/tool/jars/myagent.jar=sun -jar target/MyTest-1.0-SNAPSHOT-jar-with-dependencies.jar
输出:
开始premain sun
类 java/lang/invoke/MethodHandleImpl
类 java/lang/invoke/MethodHandleImpl$1
类 java/lang/invoke/MethodHandleImpl$2
类 java/util/function/Function
类 java/lang/invoke/MethodHandleImpl$3
类 java/lang/invoke/MethodHandleImpl$4
类 java/lang/ClassValue
类 java/lang/ClassValue$Entry
类 java/lang/ClassValue$Identity
类 java/lang/ClassValue$Version
类 java/lang/invoke/MemberName$Factory
类 java/lang/invoke/MethodHandleStatics
类 java/lang/invoke/MethodHandleStatics$1
类 sun/misc/PostVMInitHook
类 sun/usagetracker/UsageTrackerClient
类 java/util/concurrent/atomic/AtomicBoolean
类 sun/usagetracker/UsageTrackerClient$1
类 sun/usagetracker/UsageTrackerClient$4
类 sun/usagetracker/UsageTrackerClient$3
类 java/io/FileOutputStream$1
类 sun/launcher/LauncherHelper
类 sun/misc/IOUtils
类 java/util/jar/JarVerifier
类 java/security/CodeSigner
类 java/util/jar/JarVerifier$3
类 java/io/ByteArrayOutputStream
类 agent/MyClient
类 sun/launcher/LauncherHelper$FXHelper
类 java/lang/Class$MethodArray
类 java/lang/Void
my client 6
类 java/lang/Shutdown
类 java/lang/Shutdown$Lock
后续可以使用这种方式+asm或javaassist进行加载期aop
javaassist:https://www.jianshu.com/p/b2d09a78678d Javaagent技术初探
asm:https://blog.csdn.net/conquer0715/article/details/51283610
对比:https://blog.csdn.net/yczz/article/details/14497527 javasssit简单,性能差
这里我们使用javassist测试:修改一下transorm:
private ClassPool classPool = new ClassPool(true); @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if(className.equals("agent/MyClient")) { System.out.println("类 " + className); try { CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer)); for(CtBehavior ctBehavior : ctClass.getDeclaredBehaviors()) { if(ctBehavior.getLongName().equals("agent.MyClient.print()")) { System.out.println("开始处理方法 " + ctBehavior.getLongName()); ctBehavior.insertBefore("System.out.println(\"前置aop\");"); ctBehavior.insertAfter("System.out.println(\"后置aop\");"); } } return ctClass.toBytecode(); } catch (Exception e) { e.printStackTrace(); return null; } } else return null; }
public class MyClient { public static void main(String [] fl) { System.out.println("my client 6"); MyClient myClient = new MyClient(); myClient.print(); } private void print() { System.out.println("自己的"); } }
// cp target/MyTest-1.0-SNAPSHOT-jar-with-dependencies.jar ~/Documents/tool/jars/myagent.jar
// java -javaagent:/Users/sunyuming/Documents/tool/jars/myagent.jar=sun -jar target/MyTest-1.0-SNAPSHOT-jar-with-dependencies.jar
输出:
开始premain sun
类 agent/MyClient
开始处理方法 agent.MyClient.print()
my client 6
前置aop
自己的
后置aop
ps:期间:No such Class : System.out
https://stackoverflow.com/questions/26788724/javassist-cannotcompileexception-no-such-class-system-out
You create the ClassPool like:
ClassPool pool = new ClassPool();
Which creates an empty ClassPool, not even the default Java classes from rt.jar. So there is no System class defined and compilation failes.
By using:
ClassPool pool = new ClassPool(true);
You will get a ClassPool with the stuff from the classPath and system jars already added. System class is found and it compiles.