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.

posted on 2019-07-28 21:19  silyvin  阅读(925)  评论(0编辑  收藏  举报