二、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;
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技术初探
这里我们使用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.