Javaバイトコード2-instrument初体験

5101 ワード

文:オリジナル文章、転載は出典を明記してください。http://www.jianshu.com/p/be092b1c72cb
JavaバイトコードシリーズJavaバイトコード1-AgentシンプルJavaバイトコード2-instrumentはJavaバイトコード3を初めて体験します。ByteBddyを使ってJava-Agent Javaバイトコード4を実現します。Java-Agentを使ってJVM監視ツールの本シリーズのコードを実現します。https://github.com/hawkingfoo/demo-agent
一、概要
前のセクションではJavaバイトコード1−Agentを簡単に手に入れて、Agentによってmain方法の前で実行できることを理解した。本セクションでは、java.lang.instrumentによって、クラスのバイトコードが1つのAgentを実現することができるinstrumentを紹介します。次に、javassistを介して簡単な性能検出ツールを実現します。目的は、検出関数の呼び出し時に、ここではレンガを投げて玉を引くだけで、instrumentが提供するより緩やかな結合のAOPはこれだけではない。
二、一つの関数を実現して、消耗時のAgentを検出します。
1、pom.xmlを修正する
ここでは前のセクションとは異なり、Agentを強化するためにjavassistパッケージを追加的に導入する必要がある。
javassistjavassist              
    3.12.1.GA
    jar
 
この他に、このJarパッケージをAgentに包装したいです。以下の構成を参照してください。
 
  org.apache.maven.plugins  
  maven-shade-plugin  
   
     
      package  
       
        shade 
       
     
    
   
     
       
        javassist:javassist:jar: 
       
     
   


2、Agentを一つ実現する
public class MyAgent {

    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("this is an perform monitor agent.");
        //    Transformer
        ClassFileTransformer transformer = new PerformMonitorTransformer();
        inst.addTransformer(transformer);
    }
}
前のセクションとは異なり、ここにTransformerが追加されている。
3、Transformerクラスを実現する
public class PerformMonitorTransformer implements ClassFileTransformer {

    private static final Set classNameSet = new HashSet<>();
    static {
        classNameSet.add("com.example.demo.AgentTest");
    }

    @Override
    public byte[] transform(ClassLoader loader,
                            String className,
                            Class> classBeingRedefined,
                            ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {
        try {
            String currentClassName = className.replaceAll("/", ".");
            if (!classNameSet.contains(currentClassName)) { //     Set     
                return null;
            }
            System.out.println("transform: [" + currentClassName + "]");

            CtClass ctClass = ClassPool.getDefault().get(currentClassName);
            CtBehavior[] methods = ctClass.getDeclaredBehaviors();
            for (CtBehavior method : methods) {
                enhanceMethod(method);
            }
            return ctClass.toBytecode();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private void enhanceMethod(CtBehavior method) throws Exception {
        if (method.isEmpty()) {
            return;
        }
        String methodName = method.getName();
        if (methodName.equalsIgnoreCase("main")) { //    main  
            return;
        }

        final StringBuilder source = new StringBuilder();
        source.append("{")
                    .append("long start = System.nanoTime();
") // : .append("$_ = $proceed($$);
") // .append("System.out.print(\"method:[" + methodName + "]\");").append("
") .append("System.out.println(\" cost:[\" +(System.nanoTime() -start)+ \"ns]\");") // .append("}"); ExprEditor editor = new ExprEditor() { @Override public void edit(MethodCall methodCall) throws CannotCompileException { methodCall.replace(source.toString()); } }; method.instrument(editor); } }
上のコードの中で、Setに保存されているクラスの方法を強化しました。ここでは「comp.example.demo.AgentTest」です。他のクラスであれば、ここをファイルに再設定または配置する必要があります。enhanceMethod方法により、その方法のバイトコードが書き換えられ、すなわち元の方法体に対してサラウンド増強が行われた。
三、運転
前のセクションの動作と同じように、このセクションの内容を反映するために、Testクラスを修正し、二つの方法を追加しました。
public class AgentTest {

    private void fun1() {
        System.out.println("this is fun 1.");
    }

    private void fun2() {
        System.out.println("this is fun 2.");
    }

    public static void main(String[] args) {
        AgentTest test = new AgentTest();
        test.fun1();
        test.fun2();
    }

運転結果は以下の通りです。
this is an perform monitor agent.
transform: [com.example.demo.AgentTest]
this is fun 1.
method:[fun1] cost:[79024ns]
this is fun 2.
method:[fun2] cost:[41164ns]

Process finished with exit code 0
上記の運転結果から、呼び出しの2つの方法を強化し、呼び出しの時間がかかります。