二、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.